agentengine-sdk-python 0.6.9__py3-none-any.whl → 0.7.0__py3-none-any.whl

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 (149) hide show
  1. {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/METADATA +6 -4
  2. {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/RECORD +123 -128
  3. ksadk/api/client.py +19 -0
  4. ksadk/cli/cmd_create.py +34 -7
  5. ksadk/cli/cmd_hermes.py +6 -2
  6. ksadk/cli/cmd_invoke.py +208 -41
  7. ksadk/cli/cmd_mcp.py +6 -1
  8. ksadk/cli/cmd_openclaw.py +6 -2
  9. ksadk/cli/cmd_run.py +13 -6
  10. ksadk/cli/network_options.py +5 -3
  11. ksadk/configs/env_registry.py +1 -0
  12. ksadk/conversations/message_projection.py +269 -0
  13. ksadk/conversations/model_context.py +11 -1
  14. ksadk/conversations/runtime.py +81 -13
  15. ksadk/deployment/base.py +1 -1
  16. ksadk/deployment/providers/serverless.py +18 -8
  17. ksadk/memory/adk/resilient_session_service.py +268 -0
  18. ksadk/memory/adk/short_term_memory.py +21 -6
  19. ksadk/runners/adk_runner.py +974 -115
  20. ksadk/runners/langchain_runner.py +219 -7
  21. ksadk/runners/langgraph_runner.py +145 -12
  22. ksadk/runners/remote_runner.py +143 -18
  23. ksadk/server/app.py +148 -6
  24. ksadk/server/static/assets/{ArtifactsPanel-C0meuOdi.js → ArtifactsPanel-D-SSn2ee.js} +1 -1
  25. ksadk/server/static/assets/{CodeBlock-BbI4HPtJ.js → CodeBlock-P16VOLYi.js} +1 -1
  26. ksadk/server/static/assets/{MathMessageMarkdown-DMLQYoLp.js → MathMessageMarkdown-CdGiQWHR.js} +1 -1
  27. ksadk/server/static/assets/{MermaidBlock-DCR8WZ17.js → MermaidBlock-IOcal8cm.js} +6 -6
  28. ksadk/server/static/assets/{NativeTerminalPanel-CKUvWhUz.js → NativeTerminalPanel-DGSDWsei.js} +1 -1
  29. ksadk/server/static/assets/{arc-bj2rZnP5.js → arc-BA13sHc9.js} +1 -1
  30. ksadk/server/static/assets/architecture-7EHR7CIX-BvUlsA6l.js +1 -0
  31. ksadk/server/static/assets/{architectureDiagram-3BPJPVTR-2u1ltDIj.js → architectureDiagram-3BPJPVTR-DzJzgVoL.js} +1 -1
  32. ksadk/server/static/assets/{blockDiagram-GPEHLZMM-D6fvdK20.js → blockDiagram-GPEHLZMM-UTdVcZhn.js} +1 -1
  33. ksadk/server/static/assets/{c4Diagram-AAUBKEIU-BK82L5n5.js → c4Diagram-AAUBKEIU-Dix0GD4m.js} +1 -1
  34. ksadk/server/static/assets/channel-CTuydARX.js +1 -0
  35. ksadk/server/static/assets/{chunk-2J33WTMH-DLVdkeyQ.js → chunk-2J33WTMH-C2JxuZ42.js} +1 -1
  36. ksadk/server/static/assets/{chunk-4BX2VUAB-BbwsYcbW.js → chunk-4BX2VUAB-Be57QPgD.js} +1 -1
  37. ksadk/server/static/assets/{chunk-55IACEB6-DOairxzw.js → chunk-55IACEB6-WjSuy4nv.js} +1 -1
  38. ksadk/server/static/assets/{chunk-727SXJPM-hCO487gs.js → chunk-727SXJPM-BOovP-b5.js} +1 -1
  39. ksadk/server/static/assets/{chunk-AQP2D5EJ-XU8vDOuG.js → chunk-AQP2D5EJ-DOmZNmWj.js} +1 -1
  40. ksadk/server/static/assets/{chunk-FMBD7UC4-PrapyfMu.js → chunk-FMBD7UC4-DzwlodkU.js} +1 -1
  41. ksadk/server/static/assets/{chunk-ND2GUHAM-CboKhTZ3.js → chunk-ND2GUHAM-DMqAikEU.js} +1 -1
  42. ksadk/server/static/assets/chunk-QZHKN3VN-BnCydivS.js +1 -0
  43. ksadk/server/static/assets/classDiagram-4FO5ZUOK-BNHg45Be.js +1 -0
  44. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-BNHg45Be.js +1 -0
  45. ksadk/server/static/assets/{cose-bilkent-S5V4N54A-_N8bJ-EU.js → cose-bilkent-S5V4N54A-Zhy2GBnP.js} +1 -1
  46. ksadk/server/static/assets/{dagre-BM42HDAG-FoDlo5J2.js → dagre-BM42HDAG-CbKrtIUz.js} +1 -1
  47. ksadk/server/static/assets/{diagram-2AECGRRQ-BBp6ebBw.js → diagram-2AECGRRQ-BRApgQRK.js} +1 -1
  48. ksadk/server/static/assets/{diagram-5GNKFQAL-CqOvG1PR.js → diagram-5GNKFQAL-D0xbnDiK.js} +1 -1
  49. ksadk/server/static/assets/{diagram-KO2AKTUF-O9T3t6ba.js → diagram-KO2AKTUF-CVugVI9L.js} +1 -1
  50. ksadk/server/static/assets/{diagram-LMA3HP47-CGG8ekBO.js → diagram-LMA3HP47-DozUGgee.js} +1 -1
  51. ksadk/server/static/assets/{diagram-OG6HWLK6-4BmH1EEg.js → diagram-OG6HWLK6-ZOYsKlru.js} +1 -1
  52. ksadk/server/static/assets/{dist-BmuTWcm7.js → dist-2hInLS_J.js} +1 -1
  53. ksadk/server/static/assets/{dist-BMKM66B4.js → dist-8EEyGgDx.js} +1 -1
  54. ksadk/server/static/assets/{dist-Cp1_jKwf.js → dist-8_SfK1IP.js} +1 -1
  55. ksadk/server/static/assets/{dist-CZ6C3x8B.js → dist-B0NP_Z0z.js} +1 -1
  56. ksadk/server/static/assets/{dist-DLeM1OCK.js → dist-BIJVDMuB.js} +1 -1
  57. ksadk/server/static/assets/{dist-Cwhk4lwI.js → dist-BmlMPsCc.js} +1 -1
  58. ksadk/server/static/assets/{dist-D99wf6_X.js → dist-BrCcQ47T.js} +1 -1
  59. ksadk/server/static/assets/{dist-CWiTDN_V.js → dist-BssVmjIo.js} +1 -1
  60. ksadk/server/static/assets/{dist-BVzcWa6D.js → dist-C3HIIaN5.js} +1 -1
  61. ksadk/server/static/assets/{dist-DM0rEQPk.js → dist-CPBtqRTW.js} +1 -1
  62. ksadk/server/static/assets/{dist-BkJjNih3.js → dist-CkEKJFtk.js} +1 -1
  63. ksadk/server/static/assets/{dist-B67uF2uK.js → dist-D-JMdWPQ.js} +1 -1
  64. ksadk/server/static/assets/{dist-DI-oVAXw.js → dist-D-xOD048.js} +1 -1
  65. ksadk/server/static/assets/{dist-6kSZvCZh.js → dist-D57wnGWd.js} +1 -1
  66. ksadk/server/static/assets/{dist-Bk1l05Ni.js → dist-DL_NKc_z.js} +1 -1
  67. ksadk/server/static/assets/{dist-DbaH3p5h.js → dist-DMezvBGJ.js} +1 -1
  68. ksadk/server/static/assets/{dist-CiZHB6ZW.js → dist-DmFaq_Iy.js} +1 -1
  69. ksadk/server/static/assets/{dist-DLMjL01k.js → dist-Dp0k7bWI.js} +1 -1
  70. ksadk/server/static/assets/{dist-DCpZrjaK.js → dist-DpKtt4Bo.js} +1 -1
  71. ksadk/server/static/assets/{dist-D5JGcxBx.js → dist-DyOkZmze.js} +1 -1
  72. ksadk/server/static/assets/{dist-C2DKRFVK.js → dist-DyWsb78Q.js} +1 -1
  73. ksadk/server/static/assets/{dist-S4klkK5W.js → dist-gX6GOLKq.js} +1 -1
  74. ksadk/server/static/assets/{erDiagram-TEJ5UH35-oJhaci7S.js → erDiagram-TEJ5UH35-Bk-9bgE5.js} +1 -1
  75. ksadk/server/static/assets/eventmodeling-FCH6USID-DsAgWnlf.js +1 -0
  76. ksadk/server/static/assets/{flowDiagram-I6XJVG4X-DtTmUan-.js → flowDiagram-I6XJVG4X-Dn9kZVuZ.js} +1 -1
  77. ksadk/server/static/assets/{ganttDiagram-6RSMTGT7-BSsb_eij.js → ganttDiagram-6RSMTGT7-BGxXlJqm.js} +1 -1
  78. ksadk/server/static/assets/gitGraph-WXDBUCRP-BIao9Yjg.js +1 -0
  79. ksadk/server/static/assets/{gitGraphDiagram-PVQCEYII-BR5s6Pws.js → gitGraphDiagram-PVQCEYII-C7Q0X-wi.js} +1 -1
  80. ksadk/server/static/assets/{index-BH7-pC0B.js → index-C00Lwg3L.js} +6 -6
  81. ksadk/server/static/assets/info-J43DQDTF-CyipqGxU.js +1 -0
  82. ksadk/server/static/assets/{infoDiagram-5YYISTIA-DYPc5cvh.js → infoDiagram-5YYISTIA-BO_CSqq5.js} +1 -1
  83. ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-MoBdALDg.js → ishikawaDiagram-YF4QCWOH-eYLPLd46.js} +1 -1
  84. ksadk/server/static/assets/{journeyDiagram-JHISSGLW-DgLiQT8l.js → journeyDiagram-JHISSGLW-De9geSb0.js} +1 -1
  85. ksadk/server/static/assets/{kanban-definition-UN3LZRKU-CsFx112F.js → kanban-definition-UN3LZRKU-Cs80kQto.js} +1 -1
  86. ksadk/server/static/assets/{katex-DRsiwpM2.js → katex-DSrCFD8n.js} +1 -1
  87. ksadk/server/static/assets/{linear-TNRQ5Orv.js → linear-CN9sbKTF.js} +1 -1
  88. ksadk/server/static/assets/{mermaid-parser.core-D2eMVOgo.js → mermaid-parser.core-DlgSBU8l.js} +2 -2
  89. ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-BtByYFhL.js → mindmap-definition-RKZ34NQL-9afz7Oug.js} +1 -1
  90. ksadk/server/static/assets/packet-YPE3B663-CGkyRxlg.js +1 -0
  91. ksadk/server/static/assets/pie-LRSECV5Y-M0uWAf4n.js +1 -0
  92. ksadk/server/static/assets/{pieDiagram-4H26LBE5-DPnuCB8c.js → pieDiagram-4H26LBE5-DPv_VstN.js} +1 -1
  93. ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-miFpCPv4.js → quadrantDiagram-W4KKPZXB-o77wZ1XK.js} +1 -1
  94. ksadk/server/static/assets/radar-GUYGQ44K-Bdbm24-x.js +1 -0
  95. ksadk/server/static/assets/{requirementDiagram-4Y6WPE33-mjB_yYkW.js → requirementDiagram-4Y6WPE33-BYVw81VC.js} +1 -1
  96. ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-D7hSlZZZ.js → sankeyDiagram-5OEKKPKP-B7nZFMKT.js} +1 -1
  97. ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-D9Cx6hC3.js → sequenceDiagram-3UESZ5HK-BGRRrFw5.js} +1 -1
  98. ksadk/server/static/assets/{stateDiagram-AJRCARHV-D_zrg47o.js → stateDiagram-AJRCARHV-BUfHCoPs.js} +1 -1
  99. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-8xBN_gLM.js +1 -0
  100. ksadk/server/static/assets/{timeline-definition-PNZ67QCA-CwSec0Wa.js → timeline-definition-PNZ67QCA-FbCTGrf9.js} +1 -1
  101. ksadk/server/static/assets/treeView-BLDUP644-npMo0ak1.js +1 -0
  102. ksadk/server/static/assets/treemap-LRROVOQU-CHdzrrdo.js +1 -0
  103. ksadk/server/static/assets/{vennDiagram-CIIHVFJN-agCVobiF.js → vennDiagram-CIIHVFJN-DWgwtPsJ.js} +1 -1
  104. ksadk/server/static/assets/wardley-L42UT6IY-BEQz7wyy.js +1 -0
  105. ksadk/server/static/assets/{wardleyDiagram-YWT4CUSO-TmrlhWNt.js → wardleyDiagram-YWT4CUSO-2x8G-tTM.js} +1 -1
  106. ksadk/server/static/assets/{xychartDiagram-2RQKCTM6-DV71V-ah.js → xychartDiagram-2RQKCTM6-Drma3AYl.js} +1 -1
  107. ksadk/server/static/index.html +1 -1
  108. ksadk/sessions/__init__.py +17 -21
  109. ksadk/sessions/continuity.py +28 -4
  110. ksadk/sessions/postgres_service.py +77 -14
  111. ksadk/sessions/resilience.py +52 -0
  112. ksadk/sessions/resilient.py +360 -0
  113. ksadk/toolsets/__init__.py +2 -0
  114. ksadk/toolsets/workspace.py +49 -10
  115. ksadk/tracing/setup.py +63 -0
  116. ksadk/tui/__init__.py +22 -7
  117. ksadk/tui/loop.py +1877 -0
  118. ksadk/tui/stream_render.py +68 -0
  119. ksadk/version.py +1 -1
  120. ksadk/server/static/assets/architecture-7EHR7CIX-Cr6Xz1_c.js +0 -1
  121. ksadk/server/static/assets/channel-BSSOwFIb.js +0 -1
  122. ksadk/server/static/assets/chunk-QZHKN3VN-XXpdiwUL.js +0 -1
  123. ksadk/server/static/assets/classDiagram-4FO5ZUOK-C5HvGiyK.js +0 -1
  124. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-C5HvGiyK.js +0 -1
  125. ksadk/server/static/assets/eventmodeling-FCH6USID-CIgwHJLL.js +0 -1
  126. ksadk/server/static/assets/gitGraph-WXDBUCRP-BVAJ7uo0.js +0 -1
  127. ksadk/server/static/assets/info-J43DQDTF-MT80V4ge.js +0 -1
  128. ksadk/server/static/assets/packet-YPE3B663-BLKhbXZB.js +0 -1
  129. ksadk/server/static/assets/pie-LRSECV5Y-DFOikjSY.js +0 -1
  130. ksadk/server/static/assets/radar-GUYGQ44K-095b1Ikw.js +0 -1
  131. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-J9kCb0nC.js +0 -1
  132. ksadk/server/static/assets/treeView-BLDUP644-yinfJEY6.js +0 -1
  133. ksadk/server/static/assets/treemap-LRROVOQU-DosUqN1Q.js +0 -1
  134. ksadk/server/static/assets/wardley-L42UT6IY-Bvp7-5uH.js +0 -1
  135. ksadk/tui/app.py +0 -400
  136. ksadk/tui/clipboard.py +0 -100
  137. ksadk/tui/widgets/__init__.py +0 -22
  138. ksadk/tui/widgets/assistant.py +0 -89
  139. ksadk/tui/widgets/base.py +0 -54
  140. ksadk/tui/widgets/chat_input.py +0 -246
  141. ksadk/tui/widgets/history.py +0 -156
  142. ksadk/tui/widgets/system.py +0 -87
  143. ksadk/tui/widgets/thinking.py +0 -110
  144. ksadk/tui/widgets/tool_call.py +0 -161
  145. ksadk/tui/widgets/user.py +0 -37
  146. {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/WHEEL +0 -0
  147. {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/entry_points.txt +0 -0
  148. {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/licenses/LICENSE +0 -0
  149. {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/top_level.txt +0 -0
@@ -1,89 +0,0 @@
1
- """
2
- AssistantMessage - 助手消息组件
3
-
4
- 支持流式 MarkdownStream 渲染(与 deepagents-cli 一致)
5
- """
6
-
7
- from textual.containers import Vertical
8
- from textual.widgets import Markdown
9
- from textual.widgets._markdown import MarkdownStream
10
- from typing import Any, Optional
11
-
12
-
13
- class AssistantMessage(Vertical):
14
- """助手消息组件
15
-
16
- 使用 MarkdownStream 实现流式 Markdown 渲染
17
- """
18
-
19
- DEFAULT_CSS = """
20
- AssistantMessage {
21
- height: auto;
22
- padding: 0 1;
23
- margin: 1 0 0 0;
24
- color: #ffffff;
25
- }
26
-
27
- AssistantMessage Markdown {
28
- padding: 0;
29
- margin: 0;
30
- color: #ffffff;
31
- }
32
-
33
- /* 确保 Markdown 的所有子组件都有正确的颜色 */
34
- AssistantMessage Markdown * {
35
- color: #ffffff;
36
- }
37
- """
38
-
39
- def __init__(self, content: str = "", **kwargs: Any) -> None:
40
- super().__init__(**kwargs)
41
- self._content = content
42
- self._markdown: Optional[Markdown] = None
43
- self._stream: Optional[MarkdownStream] = None
44
-
45
- def compose(self):
46
- """构建助手消息布局"""
47
- yield Markdown("", id="assistant-content")
48
-
49
- def on_mount(self) -> None:
50
- """缓存 markdown widget 引用"""
51
- self._markdown = self.query_one("#assistant-content", Markdown)
52
-
53
- def _get_markdown(self) -> Markdown:
54
- """获取 markdown widget"""
55
- if self._markdown is None:
56
- self._markdown = self.query_one("#assistant-content", Markdown)
57
- return self._markdown
58
-
59
- def _ensure_stream(self) -> MarkdownStream:
60
- """确保 stream 已初始化"""
61
- if self._stream is None:
62
- self._stream = Markdown.get_stream(self._get_markdown())
63
- return self._stream
64
-
65
- async def append_content(self, text: str) -> None:
66
- """追加内容(流式渲染)"""
67
- if not text:
68
- return
69
- self._content += text
70
- stream = self._ensure_stream()
71
- await stream.write(text)
72
-
73
- async def stop_stream(self) -> None:
74
- """停止流式渲染"""
75
- if self._stream is not None:
76
- await self._stream.stop()
77
- self._stream = None
78
-
79
- async def set_content(self, content: str) -> None:
80
- """设置完整内容"""
81
- await self.stop_stream()
82
- self._content = content
83
- if self._markdown:
84
- await self._markdown.update(content)
85
-
86
- @property
87
- def content(self) -> str:
88
- return self._content
89
-
ksadk/tui/widgets/base.py DELETED
@@ -1,54 +0,0 @@
1
- """
2
- Base Widget - 消息组件基类
3
- """
4
-
5
- from textual.widget import Widget
6
- from textual.reactive import reactive
7
- from typing import Any, Optional
8
-
9
-
10
- class MessageWidget(Widget):
11
- """消息组件基类
12
-
13
- 所有消息组件的抽象基类,提供基础的消息渲染能力。
14
- """
15
-
16
- DEFAULT_CSS = """
17
- MessageWidget {
18
- width: 100%;
19
- padding: 0 1;
20
- margin: 0 0 1 0;
21
- color: #ffffff;
22
- }
23
- """
24
-
25
- content: reactive[str] = reactive("")
26
-
27
- def __init__(
28
- self,
29
- content: str = "",
30
- *,
31
- name: Optional[str] = None,
32
- id: Optional[str] = None,
33
- classes: Optional[str] = None,
34
- ) -> None:
35
- super().__init__(name=name, id=id, classes=classes)
36
- self.content = content
37
- self._is_streaming = False
38
-
39
- async def append_content(self, delta: str) -> None:
40
- """追加内容(流式渲染)
41
-
42
- Args:
43
- delta: 增量内容
44
- """
45
- self._is_streaming = True
46
- self.content += delta
47
-
48
- async def stop_stream(self) -> None:
49
- """停止流式渲染"""
50
- self._is_streaming = False
51
-
52
- def watch_content(self, new_content: str) -> None:
53
- """监听内容变化,触发重新渲染"""
54
- self.refresh()
@@ -1,246 +0,0 @@
1
- """Chat input widget with history support."""
2
-
3
- from __future__ import annotations
4
-
5
- from pathlib import Path
6
- from typing import TYPE_CHECKING, Any, ClassVar, Optional, List
7
-
8
- from textual import events
9
- from textual.binding import Binding
10
- from textual.containers import Horizontal, Vertical
11
- from textual.message import Message
12
- from textual.reactive import reactive
13
- from textual.widgets import Static, TextArea
14
-
15
- from ksadk.tui.widgets.history import HistoryManager
16
-
17
- if TYPE_CHECKING:
18
- from textual.app import ComposeResult
19
-
20
-
21
- class ChatTextArea(TextArea):
22
- """TextArea subclass with custom key handling for chat input."""
23
-
24
- BINDINGS: ClassVar[List[Binding]] = [
25
- Binding(
26
- "shift+enter,ctrl+j,alt+enter,ctrl+enter",
27
- "insert_newline",
28
- "New Line",
29
- show=False,
30
- priority=True,
31
- ),
32
- Binding(
33
- "ctrl+a",
34
- "select_all_text",
35
- "Select All",
36
- show=False,
37
- priority=True,
38
- ),
39
- ]
40
-
41
- class Submitted(Message):
42
- """Message sent when text is submitted."""
43
-
44
- def __init__(self, value: str) -> None:
45
- self.value = value
46
- super().__init__()
47
-
48
- class HistoryPrevious(Message):
49
- """Request previous history entry."""
50
-
51
- def __init__(self, current_text: str) -> None:
52
- self.current_text = current_text
53
- super().__init__()
54
-
55
- class HistoryNext(Message):
56
- """Request next history entry."""
57
-
58
- def __init__(self, **kwargs: Any) -> None:
59
- kwargs.pop("placeholder", None)
60
- super().__init__(**kwargs)
61
- self._navigating_history = False
62
-
63
- def action_insert_newline(self) -> None:
64
- """Insert a newline character."""
65
- self.insert("\n")
66
-
67
- def action_select_all_text(self) -> None:
68
- """Select all text in the text area."""
69
- if not self.text:
70
- return
71
- lines = self.text.split("\n")
72
- end_row = len(lines) - 1
73
- end_col = len(lines[end_row])
74
- self.selection = ((0, 0), (end_row, end_col))
75
-
76
- async def _on_key(self, event: events.Key) -> None:
77
- """Handle key events."""
78
- if event.key in ("shift+enter", "ctrl+j", "alt+enter", "ctrl+enter"):
79
- event.prevent_default()
80
- event.stop()
81
- self.insert("\n")
82
- return
83
-
84
- if event.key == "enter":
85
- event.prevent_default()
86
- event.stop()
87
- value = self.text.strip()
88
- if value:
89
- self.post_message(self.Submitted(value))
90
- return
91
-
92
- if event.key == "up":
93
- row, _ = self.cursor_location
94
- if row == 0:
95
- event.prevent_default()
96
- event.stop()
97
- self._navigating_history = True
98
- self.post_message(self.HistoryPrevious(self.text))
99
- return
100
-
101
- if event.key == "down":
102
- row, _ = self.cursor_location
103
- total_lines = self.text.count("\n") + 1
104
- if row == total_lines - 1:
105
- event.prevent_default()
106
- event.stop()
107
- self._navigating_history = True
108
- self.post_message(self.HistoryNext())
109
- return
110
-
111
- await super()._on_key(event)
112
-
113
- def set_text_from_history(self, text: str) -> None:
114
- """Set text from history navigation."""
115
- self._navigating_history = True
116
- self.text = text
117
- lines = text.split("\n")
118
- last_row = len(lines) - 1
119
- last_col = len(lines[last_row])
120
- self.move_cursor((last_row, last_col))
121
- self._navigating_history = False
122
-
123
- def clear_text(self) -> None:
124
- """Clear the text area."""
125
- self.text = ""
126
- self.move_cursor((0, 0))
127
-
128
-
129
- class ChatInput(Vertical):
130
- """Chat input widget with prompt indicator, multi-line text, and history."""
131
-
132
- DEFAULT_CSS = """
133
- ChatInput {
134
- height: auto;
135
- min-height: 3;
136
- max-height: 12;
137
- padding: 0;
138
- background: $surface;
139
- border: solid $primary;
140
- }
141
-
142
- ChatInput .input-row {
143
- height: auto;
144
- width: 100%;
145
- }
146
-
147
- ChatInput .input-prompt {
148
- width: 3;
149
- height: 1;
150
- padding: 0 1;
151
- color: $primary;
152
- text-style: bold;
153
- }
154
-
155
- ChatInput ChatTextArea {
156
- width: 1fr;
157
- height: auto;
158
- min-height: 1;
159
- max-height: 8;
160
- border: none;
161
- background: transparent;
162
- padding: 0;
163
- }
164
-
165
- ChatInput ChatTextArea:focus {
166
- border: none;
167
- }
168
- """
169
-
170
- class Submitted(Message):
171
- """Message sent when input is submitted."""
172
-
173
- def __init__(self, value: str, mode: str = "normal") -> None:
174
- super().__init__()
175
- self.value = value
176
- self.mode = mode
177
-
178
- mode: reactive[str] = reactive("normal")
179
-
180
- def __init__(
181
- self,
182
- cwd: Optional[str | Path] = None,
183
- history_file: Optional[Path] = None,
184
- **kwargs: Any,
185
- ) -> None:
186
- super().__init__(**kwargs)
187
- self._cwd = Path(cwd) if cwd else Path.cwd()
188
- self._text_area: Optional[ChatTextArea] = None
189
-
190
- if history_file is None:
191
- history_file = Path.home() / ".ksadk" / "history.jsonl"
192
- self._history = HistoryManager(history_file)
193
-
194
- def compose(self) -> ComposeResult:
195
- """Compose the chat input layout."""
196
- with Horizontal(classes="input-row"):
197
- yield Static(">", classes="input-prompt", id="prompt")
198
- yield ChatTextArea(id="chat-input")
199
-
200
- def on_mount(self) -> None:
201
- """Initialize components after mount."""
202
- self._text_area = self.query_one("#chat-input", ChatTextArea)
203
- self._text_area.focus()
204
-
205
- def on_chat_text_area_submitted(self, event: ChatTextArea.Submitted) -> None:
206
- """Handle text submission."""
207
- value = event.value
208
- if value:
209
- self._history.add(value)
210
- self.post_message(self.Submitted(value, self.mode))
211
- if self._text_area:
212
- self._text_area.clear_text()
213
- self.mode = "normal"
214
-
215
- def on_chat_text_area_history_previous(self, event: ChatTextArea.HistoryPrevious) -> None:
216
- """Handle history previous request."""
217
- entry = self._history.get_previous(event.current_text)
218
- if entry is not None and self._text_area:
219
- self._text_area.set_text_from_history(entry)
220
-
221
- def on_chat_text_area_history_next(self, event: ChatTextArea.HistoryNext) -> None:
222
- """Handle history next request."""
223
- entry = self._history.get_next()
224
- if entry is not None and self._text_area:
225
- self._text_area.set_text_from_history(entry)
226
-
227
- def focus_input(self) -> None:
228
- if self._text_area:
229
- self._text_area.focus()
230
-
231
- @property
232
- def value(self) -> str:
233
- if self._text_area:
234
- return self._text_area.text
235
- return ""
236
-
237
- @value.setter
238
- def value(self, val: str) -> None:
239
- if self._text_area:
240
- self._text_area.text = val
241
-
242
- def set_disabled(self, *, disabled: bool) -> None:
243
- if self._text_area:
244
- self._text_area.disabled = disabled
245
- if disabled:
246
- self._text_area.blur()
@@ -1,156 +0,0 @@
1
- """Command history manager for input persistence."""
2
-
3
- from __future__ import annotations
4
-
5
- import json
6
- from pathlib import Path # noqa: TC003 - used at runtime in type hints
7
- from typing import List, Optional
8
-
9
-
10
- class HistoryManager:
11
- """Manages command history with file persistence.
12
-
13
- Uses append-only writes for concurrent safety. Multiple agents can
14
- safely write to the same history file without corruption.
15
- """
16
-
17
- def __init__(self, history_file: Path, max_entries: int = 100) -> None:
18
- """Initialize the history manager.
19
-
20
- Args:
21
- history_file: Path to the JSON-lines history file
22
- max_entries: Maximum number of entries to keep
23
- """
24
- self.history_file = history_file
25
- self.max_entries = max_entries
26
- self._entries: List[str] = []
27
- self._current_index: int = -1
28
- self._temp_input: str = ""
29
- self._load_history()
30
-
31
- def _load_history(self) -> None:
32
- """Load history from file."""
33
- if not self.history_file.exists():
34
- return
35
-
36
- try:
37
- with self.history_file.open("r", encoding="utf-8") as f:
38
- entries = []
39
- for raw_line in f:
40
- line = raw_line.rstrip("\n\r")
41
- if not line:
42
- continue
43
- try:
44
- entry = json.loads(line)
45
- except json.JSONDecodeError:
46
- entry = line
47
- entries.append(entry if isinstance(entry, str) else str(entry))
48
- self._entries = entries[-self.max_entries :]
49
- except (OSError, UnicodeDecodeError):
50
- self._entries = []
51
-
52
- def _append_to_file(self, text: str) -> None:
53
- """Append a single entry to history file (concurrent-safe)."""
54
- try:
55
- self.history_file.parent.mkdir(parents=True, exist_ok=True)
56
- with self.history_file.open("a", encoding="utf-8") as f:
57
- f.write(json.dumps(text) + "\n")
58
- except OSError:
59
- pass
60
-
61
- def _compact_history(self) -> None:
62
- """Rewrite history file to remove old entries.
63
-
64
- Only called when entries exceed 2x max_entries to minimize rewrites.
65
- """
66
- try:
67
- self.history_file.parent.mkdir(parents=True, exist_ok=True)
68
- with self.history_file.open("w", encoding="utf-8") as f:
69
- for entry in self._entries:
70
- f.write(json.dumps(entry) + "\n")
71
- except OSError:
72
- pass
73
-
74
- def add(self, text: str) -> None:
75
- """Add a command to history.
76
-
77
- Args:
78
- text: The command text to add
79
- """
80
- text = text.strip()
81
- # Skip empty or slash commands ? No, allow slash commands in history usually.
82
- # DeepAgents code skips empty or slash commands.
83
- # But user might want to recall /clear or /thinking.
84
- # Let's keep slash commands in history for ksadk.
85
- if not text:
86
- return
87
-
88
- # Skip duplicates of the last entry
89
- if self._entries and self._entries[-1] == text:
90
- return
91
-
92
- self._entries.append(text)
93
-
94
- # Append to file (fast, concurrent-safe)
95
- self._append_to_file(text)
96
-
97
- # Compact only when we have 2x max entries (rare operation)
98
- if len(self._entries) > self.max_entries * 2:
99
- self._entries = self._entries[-self.max_entries :]
100
- self._compact_history()
101
-
102
- self.reset_navigation()
103
-
104
- def get_previous(self, current_input: str, prefix: str = "") -> Optional[str]:
105
- """Get the previous history entry.
106
-
107
- Args:
108
- current_input: Current input text (saved on first navigation)
109
- prefix: Optional prefix to filter entries
110
-
111
- Returns:
112
- Previous matching entry or None
113
- """
114
- if not self._entries:
115
- return None
116
-
117
- # Save current input on first navigation
118
- if self._current_index == -1:
119
- self._temp_input = current_input
120
- self._current_index = len(self._entries)
121
-
122
- # Search backwards for matching entry
123
- for i in range(self._current_index - 1, -1, -1):
124
- if self._entries[i].startswith(prefix):
125
- self._current_index = i
126
- return self._entries[i]
127
-
128
- return None
129
-
130
- def get_next(self, prefix: str = "") -> Optional[str]:
131
- """Get the next history entry.
132
-
133
- Args:
134
- prefix: Optional prefix to filter entries
135
-
136
- Returns:
137
- Next matching entry, original input at end, or None
138
- """
139
- if self._current_index == -1:
140
- return None
141
-
142
- # Search forwards for matching entry
143
- for i in range(self._current_index + 1, len(self._entries)):
144
- if self._entries[i].startswith(prefix):
145
- self._current_index = i
146
- return self._entries[i]
147
-
148
- # Return to original input at the end
149
- result = self._temp_input
150
- self.reset_navigation()
151
- return result
152
-
153
- def reset_navigation(self) -> None:
154
- """Reset navigation state."""
155
- self._current_index = -1
156
- self._temp_input = ""
@@ -1,87 +0,0 @@
1
- """
2
- SystemMessage - 系统消息组件
3
-
4
- 用于显示系统通知、错误等。
5
- """
6
-
7
- from textual.widgets import Static
8
- from typing import Optional
9
-
10
- from .base import MessageWidget
11
-
12
-
13
- class SystemMessage(MessageWidget):
14
- """系统消息组件
15
-
16
- 用于显示系统级通知:
17
- - 信息提示
18
- - 警告
19
- - 错误
20
- """
21
-
22
- DEFAULT_CSS = """
23
- SystemMessage {
24
- width: 100%;
25
- padding: 0 1;
26
- margin: 0 0 1 0;
27
- color: #bbbbbb;
28
- background: $surface;
29
- }
30
-
31
- SystemMessage.info {
32
- color: #66ccff;
33
- }
34
-
35
- SystemMessage.info * {
36
- color: #66ccff;
37
- }
38
-
39
- SystemMessage.warning {
40
- color: #ffcc00;
41
- }
42
-
43
- SystemMessage.warning * {
44
- color: #ffcc00;
45
- }
46
-
47
- SystemMessage.error {
48
- color: #ff6666;
49
- }
50
-
51
- SystemMessage.error * {
52
- color: #ff6666;
53
- }
54
-
55
- SystemMessage.success {
56
- color: #66ff66;
57
- }
58
-
59
- SystemMessage.success * {
60
- color: #66ff66;
61
- }
62
- """
63
-
64
- def __init__(
65
- self,
66
- content: str,
67
- level: str = "info",
68
- **kwargs
69
- ) -> None:
70
- super().__init__(content=content, **kwargs)
71
- self._level = level
72
- self.add_class(level)
73
-
74
- def compose(self):
75
- """构建组件"""
76
- icon = self._get_icon()
77
- yield Static(f"{icon} {self.content}")
78
-
79
- def _get_icon(self) -> str:
80
- """获取图标"""
81
- icons = {
82
- "info": "ℹ️",
83
- "warning": "⚠️",
84
- "error": "❌",
85
- "success": "✅",
86
- }
87
- return icons.get(self._level, "ℹ️")