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.
- {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/METADATA +6 -4
- {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/RECORD +123 -128
- ksadk/api/client.py +19 -0
- ksadk/cli/cmd_create.py +34 -7
- ksadk/cli/cmd_hermes.py +6 -2
- ksadk/cli/cmd_invoke.py +208 -41
- ksadk/cli/cmd_mcp.py +6 -1
- ksadk/cli/cmd_openclaw.py +6 -2
- ksadk/cli/cmd_run.py +13 -6
- ksadk/cli/network_options.py +5 -3
- ksadk/configs/env_registry.py +1 -0
- ksadk/conversations/message_projection.py +269 -0
- ksadk/conversations/model_context.py +11 -1
- ksadk/conversations/runtime.py +81 -13
- ksadk/deployment/base.py +1 -1
- ksadk/deployment/providers/serverless.py +18 -8
- ksadk/memory/adk/resilient_session_service.py +268 -0
- ksadk/memory/adk/short_term_memory.py +21 -6
- ksadk/runners/adk_runner.py +974 -115
- ksadk/runners/langchain_runner.py +219 -7
- ksadk/runners/langgraph_runner.py +145 -12
- ksadk/runners/remote_runner.py +143 -18
- ksadk/server/app.py +148 -6
- ksadk/server/static/assets/{ArtifactsPanel-C0meuOdi.js → ArtifactsPanel-D-SSn2ee.js} +1 -1
- ksadk/server/static/assets/{CodeBlock-BbI4HPtJ.js → CodeBlock-P16VOLYi.js} +1 -1
- ksadk/server/static/assets/{MathMessageMarkdown-DMLQYoLp.js → MathMessageMarkdown-CdGiQWHR.js} +1 -1
- ksadk/server/static/assets/{MermaidBlock-DCR8WZ17.js → MermaidBlock-IOcal8cm.js} +6 -6
- ksadk/server/static/assets/{NativeTerminalPanel-CKUvWhUz.js → NativeTerminalPanel-DGSDWsei.js} +1 -1
- ksadk/server/static/assets/{arc-bj2rZnP5.js → arc-BA13sHc9.js} +1 -1
- ksadk/server/static/assets/architecture-7EHR7CIX-BvUlsA6l.js +1 -0
- ksadk/server/static/assets/{architectureDiagram-3BPJPVTR-2u1ltDIj.js → architectureDiagram-3BPJPVTR-DzJzgVoL.js} +1 -1
- ksadk/server/static/assets/{blockDiagram-GPEHLZMM-D6fvdK20.js → blockDiagram-GPEHLZMM-UTdVcZhn.js} +1 -1
- ksadk/server/static/assets/{c4Diagram-AAUBKEIU-BK82L5n5.js → c4Diagram-AAUBKEIU-Dix0GD4m.js} +1 -1
- ksadk/server/static/assets/channel-CTuydARX.js +1 -0
- ksadk/server/static/assets/{chunk-2J33WTMH-DLVdkeyQ.js → chunk-2J33WTMH-C2JxuZ42.js} +1 -1
- ksadk/server/static/assets/{chunk-4BX2VUAB-BbwsYcbW.js → chunk-4BX2VUAB-Be57QPgD.js} +1 -1
- ksadk/server/static/assets/{chunk-55IACEB6-DOairxzw.js → chunk-55IACEB6-WjSuy4nv.js} +1 -1
- ksadk/server/static/assets/{chunk-727SXJPM-hCO487gs.js → chunk-727SXJPM-BOovP-b5.js} +1 -1
- ksadk/server/static/assets/{chunk-AQP2D5EJ-XU8vDOuG.js → chunk-AQP2D5EJ-DOmZNmWj.js} +1 -1
- ksadk/server/static/assets/{chunk-FMBD7UC4-PrapyfMu.js → chunk-FMBD7UC4-DzwlodkU.js} +1 -1
- ksadk/server/static/assets/{chunk-ND2GUHAM-CboKhTZ3.js → chunk-ND2GUHAM-DMqAikEU.js} +1 -1
- ksadk/server/static/assets/chunk-QZHKN3VN-BnCydivS.js +1 -0
- ksadk/server/static/assets/classDiagram-4FO5ZUOK-BNHg45Be.js +1 -0
- ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-BNHg45Be.js +1 -0
- ksadk/server/static/assets/{cose-bilkent-S5V4N54A-_N8bJ-EU.js → cose-bilkent-S5V4N54A-Zhy2GBnP.js} +1 -1
- ksadk/server/static/assets/{dagre-BM42HDAG-FoDlo5J2.js → dagre-BM42HDAG-CbKrtIUz.js} +1 -1
- ksadk/server/static/assets/{diagram-2AECGRRQ-BBp6ebBw.js → diagram-2AECGRRQ-BRApgQRK.js} +1 -1
- ksadk/server/static/assets/{diagram-5GNKFQAL-CqOvG1PR.js → diagram-5GNKFQAL-D0xbnDiK.js} +1 -1
- ksadk/server/static/assets/{diagram-KO2AKTUF-O9T3t6ba.js → diagram-KO2AKTUF-CVugVI9L.js} +1 -1
- ksadk/server/static/assets/{diagram-LMA3HP47-CGG8ekBO.js → diagram-LMA3HP47-DozUGgee.js} +1 -1
- ksadk/server/static/assets/{diagram-OG6HWLK6-4BmH1EEg.js → diagram-OG6HWLK6-ZOYsKlru.js} +1 -1
- ksadk/server/static/assets/{dist-BmuTWcm7.js → dist-2hInLS_J.js} +1 -1
- ksadk/server/static/assets/{dist-BMKM66B4.js → dist-8EEyGgDx.js} +1 -1
- ksadk/server/static/assets/{dist-Cp1_jKwf.js → dist-8_SfK1IP.js} +1 -1
- ksadk/server/static/assets/{dist-CZ6C3x8B.js → dist-B0NP_Z0z.js} +1 -1
- ksadk/server/static/assets/{dist-DLeM1OCK.js → dist-BIJVDMuB.js} +1 -1
- ksadk/server/static/assets/{dist-Cwhk4lwI.js → dist-BmlMPsCc.js} +1 -1
- ksadk/server/static/assets/{dist-D99wf6_X.js → dist-BrCcQ47T.js} +1 -1
- ksadk/server/static/assets/{dist-CWiTDN_V.js → dist-BssVmjIo.js} +1 -1
- ksadk/server/static/assets/{dist-BVzcWa6D.js → dist-C3HIIaN5.js} +1 -1
- ksadk/server/static/assets/{dist-DM0rEQPk.js → dist-CPBtqRTW.js} +1 -1
- ksadk/server/static/assets/{dist-BkJjNih3.js → dist-CkEKJFtk.js} +1 -1
- ksadk/server/static/assets/{dist-B67uF2uK.js → dist-D-JMdWPQ.js} +1 -1
- ksadk/server/static/assets/{dist-DI-oVAXw.js → dist-D-xOD048.js} +1 -1
- ksadk/server/static/assets/{dist-6kSZvCZh.js → dist-D57wnGWd.js} +1 -1
- ksadk/server/static/assets/{dist-Bk1l05Ni.js → dist-DL_NKc_z.js} +1 -1
- ksadk/server/static/assets/{dist-DbaH3p5h.js → dist-DMezvBGJ.js} +1 -1
- ksadk/server/static/assets/{dist-CiZHB6ZW.js → dist-DmFaq_Iy.js} +1 -1
- ksadk/server/static/assets/{dist-DLMjL01k.js → dist-Dp0k7bWI.js} +1 -1
- ksadk/server/static/assets/{dist-DCpZrjaK.js → dist-DpKtt4Bo.js} +1 -1
- ksadk/server/static/assets/{dist-D5JGcxBx.js → dist-DyOkZmze.js} +1 -1
- ksadk/server/static/assets/{dist-C2DKRFVK.js → dist-DyWsb78Q.js} +1 -1
- ksadk/server/static/assets/{dist-S4klkK5W.js → dist-gX6GOLKq.js} +1 -1
- ksadk/server/static/assets/{erDiagram-TEJ5UH35-oJhaci7S.js → erDiagram-TEJ5UH35-Bk-9bgE5.js} +1 -1
- ksadk/server/static/assets/eventmodeling-FCH6USID-DsAgWnlf.js +1 -0
- ksadk/server/static/assets/{flowDiagram-I6XJVG4X-DtTmUan-.js → flowDiagram-I6XJVG4X-Dn9kZVuZ.js} +1 -1
- ksadk/server/static/assets/{ganttDiagram-6RSMTGT7-BSsb_eij.js → ganttDiagram-6RSMTGT7-BGxXlJqm.js} +1 -1
- ksadk/server/static/assets/gitGraph-WXDBUCRP-BIao9Yjg.js +1 -0
- ksadk/server/static/assets/{gitGraphDiagram-PVQCEYII-BR5s6Pws.js → gitGraphDiagram-PVQCEYII-C7Q0X-wi.js} +1 -1
- ksadk/server/static/assets/{index-BH7-pC0B.js → index-C00Lwg3L.js} +6 -6
- ksadk/server/static/assets/info-J43DQDTF-CyipqGxU.js +1 -0
- ksadk/server/static/assets/{infoDiagram-5YYISTIA-DYPc5cvh.js → infoDiagram-5YYISTIA-BO_CSqq5.js} +1 -1
- ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-MoBdALDg.js → ishikawaDiagram-YF4QCWOH-eYLPLd46.js} +1 -1
- ksadk/server/static/assets/{journeyDiagram-JHISSGLW-DgLiQT8l.js → journeyDiagram-JHISSGLW-De9geSb0.js} +1 -1
- ksadk/server/static/assets/{kanban-definition-UN3LZRKU-CsFx112F.js → kanban-definition-UN3LZRKU-Cs80kQto.js} +1 -1
- ksadk/server/static/assets/{katex-DRsiwpM2.js → katex-DSrCFD8n.js} +1 -1
- ksadk/server/static/assets/{linear-TNRQ5Orv.js → linear-CN9sbKTF.js} +1 -1
- ksadk/server/static/assets/{mermaid-parser.core-D2eMVOgo.js → mermaid-parser.core-DlgSBU8l.js} +2 -2
- ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-BtByYFhL.js → mindmap-definition-RKZ34NQL-9afz7Oug.js} +1 -1
- ksadk/server/static/assets/packet-YPE3B663-CGkyRxlg.js +1 -0
- ksadk/server/static/assets/pie-LRSECV5Y-M0uWAf4n.js +1 -0
- ksadk/server/static/assets/{pieDiagram-4H26LBE5-DPnuCB8c.js → pieDiagram-4H26LBE5-DPv_VstN.js} +1 -1
- ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-miFpCPv4.js → quadrantDiagram-W4KKPZXB-o77wZ1XK.js} +1 -1
- ksadk/server/static/assets/radar-GUYGQ44K-Bdbm24-x.js +1 -0
- ksadk/server/static/assets/{requirementDiagram-4Y6WPE33-mjB_yYkW.js → requirementDiagram-4Y6WPE33-BYVw81VC.js} +1 -1
- ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-D7hSlZZZ.js → sankeyDiagram-5OEKKPKP-B7nZFMKT.js} +1 -1
- ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-D9Cx6hC3.js → sequenceDiagram-3UESZ5HK-BGRRrFw5.js} +1 -1
- ksadk/server/static/assets/{stateDiagram-AJRCARHV-D_zrg47o.js → stateDiagram-AJRCARHV-BUfHCoPs.js} +1 -1
- ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-8xBN_gLM.js +1 -0
- ksadk/server/static/assets/{timeline-definition-PNZ67QCA-CwSec0Wa.js → timeline-definition-PNZ67QCA-FbCTGrf9.js} +1 -1
- ksadk/server/static/assets/treeView-BLDUP644-npMo0ak1.js +1 -0
- ksadk/server/static/assets/treemap-LRROVOQU-CHdzrrdo.js +1 -0
- ksadk/server/static/assets/{vennDiagram-CIIHVFJN-agCVobiF.js → vennDiagram-CIIHVFJN-DWgwtPsJ.js} +1 -1
- ksadk/server/static/assets/wardley-L42UT6IY-BEQz7wyy.js +1 -0
- ksadk/server/static/assets/{wardleyDiagram-YWT4CUSO-TmrlhWNt.js → wardleyDiagram-YWT4CUSO-2x8G-tTM.js} +1 -1
- ksadk/server/static/assets/{xychartDiagram-2RQKCTM6-DV71V-ah.js → xychartDiagram-2RQKCTM6-Drma3AYl.js} +1 -1
- ksadk/server/static/index.html +1 -1
- ksadk/sessions/__init__.py +17 -21
- ksadk/sessions/continuity.py +28 -4
- ksadk/sessions/postgres_service.py +77 -14
- ksadk/sessions/resilience.py +52 -0
- ksadk/sessions/resilient.py +360 -0
- ksadk/toolsets/__init__.py +2 -0
- ksadk/toolsets/workspace.py +49 -10
- ksadk/tracing/setup.py +63 -0
- ksadk/tui/__init__.py +22 -7
- ksadk/tui/loop.py +1877 -0
- ksadk/tui/stream_render.py +68 -0
- ksadk/version.py +1 -1
- ksadk/server/static/assets/architecture-7EHR7CIX-Cr6Xz1_c.js +0 -1
- ksadk/server/static/assets/channel-BSSOwFIb.js +0 -1
- ksadk/server/static/assets/chunk-QZHKN3VN-XXpdiwUL.js +0 -1
- ksadk/server/static/assets/classDiagram-4FO5ZUOK-C5HvGiyK.js +0 -1
- ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-C5HvGiyK.js +0 -1
- ksadk/server/static/assets/eventmodeling-FCH6USID-CIgwHJLL.js +0 -1
- ksadk/server/static/assets/gitGraph-WXDBUCRP-BVAJ7uo0.js +0 -1
- ksadk/server/static/assets/info-J43DQDTF-MT80V4ge.js +0 -1
- ksadk/server/static/assets/packet-YPE3B663-BLKhbXZB.js +0 -1
- ksadk/server/static/assets/pie-LRSECV5Y-DFOikjSY.js +0 -1
- ksadk/server/static/assets/radar-GUYGQ44K-095b1Ikw.js +0 -1
- ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-J9kCb0nC.js +0 -1
- ksadk/server/static/assets/treeView-BLDUP644-yinfJEY6.js +0 -1
- ksadk/server/static/assets/treemap-LRROVOQU-DosUqN1Q.js +0 -1
- ksadk/server/static/assets/wardley-L42UT6IY-Bvp7-5uH.js +0 -1
- ksadk/tui/app.py +0 -400
- ksadk/tui/clipboard.py +0 -100
- ksadk/tui/widgets/__init__.py +0 -22
- ksadk/tui/widgets/assistant.py +0 -89
- ksadk/tui/widgets/base.py +0 -54
- ksadk/tui/widgets/chat_input.py +0 -246
- ksadk/tui/widgets/history.py +0 -156
- ksadk/tui/widgets/system.py +0 -87
- ksadk/tui/widgets/thinking.py +0 -110
- ksadk/tui/widgets/tool_call.py +0 -161
- ksadk/tui/widgets/user.py +0 -37
- {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/WHEEL +0 -0
- {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/entry_points.txt +0 -0
- {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/licenses/LICENSE +0 -0
- {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/top_level.txt +0 -0
ksadk/tui/widgets/assistant.py
DELETED
|
@@ -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()
|
ksadk/tui/widgets/chat_input.py
DELETED
|
@@ -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()
|
ksadk/tui/widgets/history.py
DELETED
|
@@ -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 = ""
|
ksadk/tui/widgets/system.py
DELETED
|
@@ -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, "ℹ️")
|