bhu-cli 1.46.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 (258) hide show
  1. bhu_cli-1.46.0.dist-info/METADATA +210 -0
  2. bhu_cli-1.46.0.dist-info/RECORD +258 -0
  3. bhu_cli-1.46.0.dist-info/WHEEL +4 -0
  4. bhu_cli-1.46.0.dist-info/entry_points.txt +4 -0
  5. kimi_cli/CHANGELOG.md +1281 -0
  6. kimi_cli/__init__.py +29 -0
  7. kimi_cli/__main__.py +43 -0
  8. kimi_cli/_build_info.py +1 -0
  9. kimi_cli/acp/AGENTS.md +92 -0
  10. kimi_cli/acp/__init__.py +13 -0
  11. kimi_cli/acp/convert.py +128 -0
  12. kimi_cli/acp/kaos.py +291 -0
  13. kimi_cli/acp/mcp.py +46 -0
  14. kimi_cli/acp/server.py +468 -0
  15. kimi_cli/acp/session.py +499 -0
  16. kimi_cli/acp/tools.py +167 -0
  17. kimi_cli/acp/types.py +13 -0
  18. kimi_cli/acp/version.py +45 -0
  19. kimi_cli/agents/default/agent.yaml +36 -0
  20. kimi_cli/agents/default/coder.yaml +25 -0
  21. kimi_cli/agents/default/explore.yaml +46 -0
  22. kimi_cli/agents/default/plan.yaml +30 -0
  23. kimi_cli/agents/default/system.md +160 -0
  24. kimi_cli/agents/okabe/agent.yaml +22 -0
  25. kimi_cli/agentspec.py +160 -0
  26. kimi_cli/app.py +807 -0
  27. kimi_cli/approval_runtime/__init__.py +29 -0
  28. kimi_cli/approval_runtime/models.py +42 -0
  29. kimi_cli/approval_runtime/runtime.py +235 -0
  30. kimi_cli/auth/__init__.py +5 -0
  31. kimi_cli/auth/oauth.py +1092 -0
  32. kimi_cli/auth/platforms.py +374 -0
  33. kimi_cli/background/__init__.py +36 -0
  34. kimi_cli/background/agent_runner.py +231 -0
  35. kimi_cli/background/ids.py +19 -0
  36. kimi_cli/background/manager.py +725 -0
  37. kimi_cli/background/models.py +105 -0
  38. kimi_cli/background/store.py +237 -0
  39. kimi_cli/background/summary.py +66 -0
  40. kimi_cli/background/worker.py +209 -0
  41. kimi_cli/cli/__init__.py +1081 -0
  42. kimi_cli/cli/__main__.py +34 -0
  43. kimi_cli/cli/_lazy_group.py +238 -0
  44. kimi_cli/cli/export.py +322 -0
  45. kimi_cli/cli/info.py +62 -0
  46. kimi_cli/cli/mcp.py +353 -0
  47. kimi_cli/cli/plugin.py +347 -0
  48. kimi_cli/cli/toad.py +73 -0
  49. kimi_cli/cli/vis.py +38 -0
  50. kimi_cli/cli/web.py +80 -0
  51. kimi_cli/config.py +429 -0
  52. kimi_cli/constant.py +116 -0
  53. kimi_cli/exception.py +43 -0
  54. kimi_cli/hooks/__init__.py +24 -0
  55. kimi_cli/hooks/config.py +34 -0
  56. kimi_cli/hooks/defaults/rtk.py +48 -0
  57. kimi_cli/hooks/engine.py +394 -0
  58. kimi_cli/hooks/events.py +190 -0
  59. kimi_cli/hooks/runner.py +103 -0
  60. kimi_cli/llm.py +333 -0
  61. kimi_cli/mcp_oauth.py +72 -0
  62. kimi_cli/metadata.py +79 -0
  63. kimi_cli/notifications/__init__.py +33 -0
  64. kimi_cli/notifications/llm.py +77 -0
  65. kimi_cli/notifications/manager.py +145 -0
  66. kimi_cli/notifications/models.py +50 -0
  67. kimi_cli/notifications/notifier.py +41 -0
  68. kimi_cli/notifications/store.py +118 -0
  69. kimi_cli/notifications/wire.py +21 -0
  70. kimi_cli/plugin/__init__.py +124 -0
  71. kimi_cli/plugin/manager.py +153 -0
  72. kimi_cli/plugin/tool.py +173 -0
  73. kimi_cli/prompts/__init__.py +6 -0
  74. kimi_cli/prompts/compact.md +73 -0
  75. kimi_cli/prompts/init.md +21 -0
  76. kimi_cli/py.typed +0 -0
  77. kimi_cli/session.py +319 -0
  78. kimi_cli/session_fork.py +325 -0
  79. kimi_cli/session_state.py +132 -0
  80. kimi_cli/share.py +14 -0
  81. kimi_cli/skill/__init__.py +727 -0
  82. kimi_cli/skill/flow/__init__.py +99 -0
  83. kimi_cli/skill/flow/d2.py +482 -0
  84. kimi_cli/skill/flow/mermaid.py +266 -0
  85. kimi_cli/skills/kimi-cli-help/SKILL.md +55 -0
  86. kimi_cli/skills/skill-creator/SKILL.md +367 -0
  87. kimi_cli/soul/__init__.py +304 -0
  88. kimi_cli/soul/agent.py +519 -0
  89. kimi_cli/soul/approval.py +267 -0
  90. kimi_cli/soul/btw.py +237 -0
  91. kimi_cli/soul/compaction.py +189 -0
  92. kimi_cli/soul/context.py +339 -0
  93. kimi_cli/soul/denwarenji.py +39 -0
  94. kimi_cli/soul/dynamic_injection.py +84 -0
  95. kimi_cli/soul/dynamic_injections/__init__.py +0 -0
  96. kimi_cli/soul/dynamic_injections/afk_mode.py +74 -0
  97. kimi_cli/soul/dynamic_injections/plan_mode.py +245 -0
  98. kimi_cli/soul/kimisoul.py +1710 -0
  99. kimi_cli/soul/message.py +92 -0
  100. kimi_cli/soul/slash.py +341 -0
  101. kimi_cli/soul/toolset.py +891 -0
  102. kimi_cli/subagents/__init__.py +21 -0
  103. kimi_cli/subagents/builder.py +42 -0
  104. kimi_cli/subagents/core.py +86 -0
  105. kimi_cli/subagents/git_context.py +170 -0
  106. kimi_cli/subagents/models.py +54 -0
  107. kimi_cli/subagents/output.py +71 -0
  108. kimi_cli/subagents/registry.py +28 -0
  109. kimi_cli/subagents/runner.py +428 -0
  110. kimi_cli/subagents/store.py +196 -0
  111. kimi_cli/telemetry/__init__.py +197 -0
  112. kimi_cli/telemetry/crash.py +149 -0
  113. kimi_cli/telemetry/sink.py +143 -0
  114. kimi_cli/telemetry/transport.py +318 -0
  115. kimi_cli/tools/AGENTS.md +5 -0
  116. kimi_cli/tools/__init__.py +105 -0
  117. kimi_cli/tools/agent/__init__.py +277 -0
  118. kimi_cli/tools/agent/description.md +41 -0
  119. kimi_cli/tools/ask_user/__init__.py +154 -0
  120. kimi_cli/tools/ask_user/description.md +19 -0
  121. kimi_cli/tools/background/__init__.py +318 -0
  122. kimi_cli/tools/background/list.md +10 -0
  123. kimi_cli/tools/background/output.md +11 -0
  124. kimi_cli/tools/background/stop.md +8 -0
  125. kimi_cli/tools/display.py +46 -0
  126. kimi_cli/tools/dmail/__init__.py +38 -0
  127. kimi_cli/tools/dmail/dmail.md +17 -0
  128. kimi_cli/tools/file/__init__.py +30 -0
  129. kimi_cli/tools/file/glob.md +21 -0
  130. kimi_cli/tools/file/glob.py +178 -0
  131. kimi_cli/tools/file/grep.md +6 -0
  132. kimi_cli/tools/file/grep_local.py +590 -0
  133. kimi_cli/tools/file/plan_mode.py +45 -0
  134. kimi_cli/tools/file/read.md +16 -0
  135. kimi_cli/tools/file/read.py +300 -0
  136. kimi_cli/tools/file/read_media.md +24 -0
  137. kimi_cli/tools/file/read_media.py +217 -0
  138. kimi_cli/tools/file/replace.md +7 -0
  139. kimi_cli/tools/file/replace.py +195 -0
  140. kimi_cli/tools/file/utils.py +257 -0
  141. kimi_cli/tools/file/write.md +5 -0
  142. kimi_cli/tools/file/write.py +177 -0
  143. kimi_cli/tools/plan/__init__.py +339 -0
  144. kimi_cli/tools/plan/description.md +29 -0
  145. kimi_cli/tools/plan/enter.py +197 -0
  146. kimi_cli/tools/plan/enter_description.md +35 -0
  147. kimi_cli/tools/plan/heroes.py +277 -0
  148. kimi_cli/tools/shell/__init__.py +260 -0
  149. kimi_cli/tools/shell/bash.md +35 -0
  150. kimi_cli/tools/test.py +55 -0
  151. kimi_cli/tools/think/__init__.py +21 -0
  152. kimi_cli/tools/think/think.md +1 -0
  153. kimi_cli/tools/todo/__init__.py +168 -0
  154. kimi_cli/tools/todo/set_todo_list.md +23 -0
  155. kimi_cli/tools/utils.py +199 -0
  156. kimi_cli/tools/web/__init__.py +4 -0
  157. kimi_cli/tools/web/fetch.md +1 -0
  158. kimi_cli/tools/web/fetch.py +189 -0
  159. kimi_cli/tools/web/search.md +1 -0
  160. kimi_cli/tools/web/search.py +163 -0
  161. kimi_cli/ui/__init__.py +0 -0
  162. kimi_cli/ui/acp/__init__.py +99 -0
  163. kimi_cli/ui/print/__init__.py +474 -0
  164. kimi_cli/ui/print/visualize.py +194 -0
  165. kimi_cli/ui/shell/__init__.py +1540 -0
  166. kimi_cli/ui/shell/console.py +109 -0
  167. kimi_cli/ui/shell/debug.py +190 -0
  168. kimi_cli/ui/shell/echo.py +17 -0
  169. kimi_cli/ui/shell/export_import.py +117 -0
  170. kimi_cli/ui/shell/keyboard.py +300 -0
  171. kimi_cli/ui/shell/mcp_status.py +111 -0
  172. kimi_cli/ui/shell/oauth.py +149 -0
  173. kimi_cli/ui/shell/placeholders.py +531 -0
  174. kimi_cli/ui/shell/prompt.py +2259 -0
  175. kimi_cli/ui/shell/replay.py +215 -0
  176. kimi_cli/ui/shell/session_picker.py +227 -0
  177. kimi_cli/ui/shell/setup.py +212 -0
  178. kimi_cli/ui/shell/slash.py +893 -0
  179. kimi_cli/ui/shell/startup.py +32 -0
  180. kimi_cli/ui/shell/task_browser.py +486 -0
  181. kimi_cli/ui/shell/update.py +349 -0
  182. kimi_cli/ui/shell/usage.py +295 -0
  183. kimi_cli/ui/shell/visualize/__init__.py +165 -0
  184. kimi_cli/ui/shell/visualize/_approval_panel.py +505 -0
  185. kimi_cli/ui/shell/visualize/_blocks.py +640 -0
  186. kimi_cli/ui/shell/visualize/_btw_panel.py +224 -0
  187. kimi_cli/ui/shell/visualize/_input_router.py +48 -0
  188. kimi_cli/ui/shell/visualize/_interactive.py +524 -0
  189. kimi_cli/ui/shell/visualize/_live_view.py +902 -0
  190. kimi_cli/ui/shell/visualize/_question_panel.py +586 -0
  191. kimi_cli/ui/theme.py +241 -0
  192. kimi_cli/utils/__init__.py +0 -0
  193. kimi_cli/utils/aiohttp.py +24 -0
  194. kimi_cli/utils/aioqueue.py +72 -0
  195. kimi_cli/utils/broadcast.py +37 -0
  196. kimi_cli/utils/changelog.py +108 -0
  197. kimi_cli/utils/clipboard.py +246 -0
  198. kimi_cli/utils/datetime.py +64 -0
  199. kimi_cli/utils/diff.py +135 -0
  200. kimi_cli/utils/editor.py +91 -0
  201. kimi_cli/utils/environment.py +225 -0
  202. kimi_cli/utils/envvar.py +22 -0
  203. kimi_cli/utils/export.py +696 -0
  204. kimi_cli/utils/file_filter.py +367 -0
  205. kimi_cli/utils/frontmatter.py +70 -0
  206. kimi_cli/utils/io.py +27 -0
  207. kimi_cli/utils/logging.py +124 -0
  208. kimi_cli/utils/media_tags.py +29 -0
  209. kimi_cli/utils/message.py +24 -0
  210. kimi_cli/utils/path.py +245 -0
  211. kimi_cli/utils/proctitle.py +33 -0
  212. kimi_cli/utils/proxy.py +31 -0
  213. kimi_cli/utils/pyinstaller.py +43 -0
  214. kimi_cli/utils/rich/__init__.py +33 -0
  215. kimi_cli/utils/rich/columns.py +99 -0
  216. kimi_cli/utils/rich/diff_render.py +481 -0
  217. kimi_cli/utils/rich/markdown.py +898 -0
  218. kimi_cli/utils/rich/markdown_sample.md +108 -0
  219. kimi_cli/utils/rich/markdown_sample_short.md +2 -0
  220. kimi_cli/utils/rich/syntax.py +114 -0
  221. kimi_cli/utils/sensitive.py +54 -0
  222. kimi_cli/utils/server.py +121 -0
  223. kimi_cli/utils/shell_quoting.py +38 -0
  224. kimi_cli/utils/signals.py +43 -0
  225. kimi_cli/utils/slashcmd.py +145 -0
  226. kimi_cli/utils/string.py +41 -0
  227. kimi_cli/utils/subprocess_env.py +73 -0
  228. kimi_cli/utils/term.py +168 -0
  229. kimi_cli/utils/typing.py +20 -0
  230. kimi_cli/utils/windows_paths.py +48 -0
  231. kimi_cli/vis/__init__.py +0 -0
  232. kimi_cli/vis/api/__init__.py +5 -0
  233. kimi_cli/vis/api/sessions.py +687 -0
  234. kimi_cli/vis/api/statistics.py +209 -0
  235. kimi_cli/vis/api/system.py +19 -0
  236. kimi_cli/vis/app.py +175 -0
  237. kimi_cli/web/__init__.py +5 -0
  238. kimi_cli/web/api/__init__.py +15 -0
  239. kimi_cli/web/api/config.py +208 -0
  240. kimi_cli/web/api/open_in.py +197 -0
  241. kimi_cli/web/api/sessions.py +1223 -0
  242. kimi_cli/web/app.py +451 -0
  243. kimi_cli/web/auth.py +191 -0
  244. kimi_cli/web/models.py +98 -0
  245. kimi_cli/web/runner/__init__.py +5 -0
  246. kimi_cli/web/runner/messages.py +57 -0
  247. kimi_cli/web/runner/process.py +754 -0
  248. kimi_cli/web/runner/worker.py +95 -0
  249. kimi_cli/web/store/__init__.py +1 -0
  250. kimi_cli/web/store/sessions.py +432 -0
  251. kimi_cli/wire/__init__.py +148 -0
  252. kimi_cli/wire/file.py +151 -0
  253. kimi_cli/wire/jsonrpc.py +263 -0
  254. kimi_cli/wire/protocol.py +2 -0
  255. kimi_cli/wire/root_hub.py +27 -0
  256. kimi_cli/wire/serde.py +26 -0
  257. kimi_cli/wire/server.py +1060 -0
  258. kimi_cli/wire/types.py +717 -0
@@ -0,0 +1,29 @@
1
+ from kimi_cli.approval_runtime.models import (
2
+ ApprovalRequestRecord,
3
+ ApprovalResponseKind,
4
+ ApprovalRuntimeEvent,
5
+ ApprovalSource,
6
+ ApprovalSourceKind,
7
+ ApprovalStatus,
8
+ )
9
+ from kimi_cli.approval_runtime.runtime import (
10
+ ApprovalCancelledError,
11
+ ApprovalRuntime,
12
+ get_current_approval_source_or_none,
13
+ reset_current_approval_source,
14
+ set_current_approval_source,
15
+ )
16
+
17
+ __all__ = [
18
+ "ApprovalCancelledError",
19
+ "ApprovalRequestRecord",
20
+ "ApprovalResponseKind",
21
+ "ApprovalRuntime",
22
+ "ApprovalRuntimeEvent",
23
+ "ApprovalSource",
24
+ "ApprovalSourceKind",
25
+ "ApprovalStatus",
26
+ "get_current_approval_source_or_none",
27
+ "reset_current_approval_source",
28
+ "set_current_approval_source",
29
+ ]
@@ -0,0 +1,42 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from dataclasses import dataclass, field
5
+ from typing import Literal
6
+
7
+ from kimi_cli.wire.types import DisplayBlock
8
+
9
+ type ApprovalResponseKind = Literal["approve", "approve_for_session", "reject"]
10
+ type ApprovalSourceKind = Literal["foreground_turn", "background_agent"]
11
+ type ApprovalStatus = Literal["pending", "resolved", "cancelled"]
12
+ type ApprovalRuntimeEventKind = Literal["request_created", "request_resolved"]
13
+
14
+
15
+ @dataclass(frozen=True, slots=True, kw_only=True)
16
+ class ApprovalSource:
17
+ kind: ApprovalSourceKind
18
+ id: str
19
+ agent_id: str | None = None
20
+ subagent_type: str | None = None
21
+
22
+
23
+ @dataclass(slots=True, kw_only=True)
24
+ class ApprovalRequestRecord:
25
+ id: str
26
+ tool_call_id: str
27
+ sender: str
28
+ action: str
29
+ description: str
30
+ display: list[DisplayBlock]
31
+ source: ApprovalSource
32
+ created_at: float = field(default_factory=time.time)
33
+ status: ApprovalStatus = "pending"
34
+ resolved_at: float | None = None
35
+ response: ApprovalResponseKind | None = None
36
+ feedback: str = ""
37
+
38
+
39
+ @dataclass(frozen=True, slots=True, kw_only=True)
40
+ class ApprovalRuntimeEvent:
41
+ kind: ApprovalRuntimeEventKind
42
+ request: ApprovalRequestRecord
@@ -0,0 +1,235 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import uuid
5
+ from contextvars import ContextVar, Token
6
+ from typing import TYPE_CHECKING
7
+
8
+ from kimi_cli.utils.logging import logger
9
+ from kimi_cli.wire.types import ApprovalRequest, ApprovalResponse
10
+
11
+ from .models import (
12
+ ApprovalRequestRecord,
13
+ ApprovalResponseKind,
14
+ ApprovalRuntimeEvent,
15
+ ApprovalSource,
16
+ ApprovalSourceKind,
17
+ )
18
+
19
+ if TYPE_CHECKING:
20
+ from collections.abc import Callable
21
+
22
+ from kimi_cli.wire.root_hub import RootWireHub
23
+ from kimi_cli.wire.types import DisplayBlock
24
+
25
+
26
+ class ApprovalCancelledError(Exception):
27
+ """Raised when a pending approval is cancelled by its source lifecycle."""
28
+
29
+
30
+ _current_approval_source = ContextVar[ApprovalSource | None](
31
+ "current_approval_source",
32
+ default=None,
33
+ )
34
+
35
+
36
+ def get_current_approval_source_or_none() -> ApprovalSource | None:
37
+ return _current_approval_source.get()
38
+
39
+
40
+ def set_current_approval_source(source: ApprovalSource) -> Token[ApprovalSource | None]:
41
+ return _current_approval_source.set(source)
42
+
43
+
44
+ def reset_current_approval_source(token: Token[ApprovalSource | None]) -> None:
45
+ _current_approval_source.reset(token)
46
+
47
+
48
+ class ApprovalRuntime:
49
+ def __init__(self) -> None:
50
+ self._requests: dict[str, ApprovalRequestRecord] = {}
51
+ self._waiters: dict[str, asyncio.Future[tuple[ApprovalResponseKind, str]]] = {}
52
+ self._waiter_counts: dict[str, int] = {}
53
+ self._subscribers: dict[str, Callable[[ApprovalRuntimeEvent], None]] = {}
54
+ self._root_wire_hub: RootWireHub | None = None
55
+
56
+ def bind_root_wire_hub(self, root_wire_hub: RootWireHub) -> None:
57
+ if self._root_wire_hub is root_wire_hub:
58
+ return
59
+ self._root_wire_hub = root_wire_hub
60
+
61
+ def create_request(
62
+ self,
63
+ *,
64
+ sender: str,
65
+ action: str,
66
+ description: str,
67
+ tool_call_id: str,
68
+ display: list[DisplayBlock],
69
+ source: ApprovalSource,
70
+ request_id: str | None = None,
71
+ ) -> ApprovalRequestRecord:
72
+ request = ApprovalRequestRecord(
73
+ id=request_id or str(uuid.uuid4()),
74
+ tool_call_id=tool_call_id,
75
+ sender=sender,
76
+ action=action,
77
+ description=description,
78
+ display=display,
79
+ source=source,
80
+ )
81
+ self._requests[request.id] = request
82
+ self._publish_event(ApprovalRuntimeEvent(kind="request_created", request=request))
83
+ self._publish_wire_request(request)
84
+ return request
85
+
86
+ async def wait_for_response(
87
+ self, request_id: str, timeout: float | None = None
88
+ ) -> tuple[ApprovalResponseKind, str]:
89
+ waiter = self._waiters.get(request_id)
90
+ request = self._requests.get(request_id)
91
+ if request is None:
92
+ raise KeyError(f"Approval request not found: {request_id}")
93
+ if waiter is None:
94
+ if request.status == "cancelled":
95
+ raise ApprovalCancelledError(request_id)
96
+ if request.status == "resolved":
97
+ assert request.response is not None
98
+ return request.response, request.feedback
99
+ waiter = asyncio.get_running_loop().create_future()
100
+ self._waiters[request_id] = waiter
101
+ self._waiter_counts[request_id] = self._waiter_counts.get(request_id, 0) + 1
102
+ try:
103
+ if timeout is None:
104
+ return await asyncio.shield(waiter)
105
+ return await asyncio.wait_for(asyncio.shield(waiter), timeout=timeout)
106
+ except TimeoutError:
107
+ logger.warning(
108
+ "Approval request {id} timed out after {t}s",
109
+ id=request_id,
110
+ t=timeout,
111
+ )
112
+ # If this timeout is the only remaining observer, drop the shared
113
+ # waiter before cancelling so we do not later set_exception on an
114
+ # unobserved future. If other observers still exist, keep the
115
+ # shared waiter registered so they receive the cancellation too.
116
+ if self._waiter_counts.get(request_id, 0) <= 1:
117
+ self._waiters.pop(request_id, None)
118
+ self._cancel_request(request_id, feedback="approval timed out")
119
+ raise ApprovalCancelledError(request_id) from None
120
+ finally:
121
+ remaining = self._waiter_counts.get(request_id, 0) - 1
122
+ if remaining > 0:
123
+ self._waiter_counts[request_id] = remaining
124
+ else:
125
+ self._waiter_counts.pop(request_id, None)
126
+ if request.status == "pending" and self._waiters.get(request_id) is waiter:
127
+ self._waiters.pop(request_id, None)
128
+
129
+ def resolve(self, request_id: str, response: ApprovalResponseKind, feedback: str = "") -> bool:
130
+ request = self._requests.get(request_id)
131
+ if request is None or request.status != "pending":
132
+ return False
133
+ request.status = "resolved"
134
+ request.response = response
135
+ request.feedback = feedback
136
+ import time
137
+
138
+ request.resolved_at = time.time()
139
+ waiter = self._waiters.pop(request_id, None)
140
+ if waiter is not None and not waiter.done():
141
+ waiter.set_result((response, feedback))
142
+ self._publish_event(ApprovalRuntimeEvent(kind="request_resolved", request=request))
143
+ self._publish_wire_response(request_id, response, feedback)
144
+ return True
145
+
146
+ def _cancel_request(self, request_id: str, feedback: str = "") -> None:
147
+ """Cancel a single pending request by ID."""
148
+ import time
149
+
150
+ request = self._requests.get(request_id)
151
+ if request is None or request.status != "pending":
152
+ return
153
+ request.status = "cancelled"
154
+ request.response = "reject"
155
+ request.feedback = feedback
156
+ request.resolved_at = time.time()
157
+ waiter = self._waiters.pop(request_id, None)
158
+ if waiter is not None and not waiter.done():
159
+ waiter.set_exception(ApprovalCancelledError(request_id))
160
+ self._publish_event(ApprovalRuntimeEvent(kind="request_resolved", request=request))
161
+ self._publish_wire_response(request_id, "reject", feedback)
162
+
163
+ def cancel_by_source(self, source_kind: ApprovalSourceKind, source_id: str) -> int:
164
+ cancelled = 0
165
+ import time
166
+
167
+ for request_id, request in self._requests.items():
168
+ if request.status != "pending":
169
+ continue
170
+ if request.source.kind != source_kind or request.source.id != source_id:
171
+ continue
172
+ request.status = "cancelled"
173
+ request.response = "reject"
174
+ request.resolved_at = time.time()
175
+ waiter = self._waiters.pop(request_id, None)
176
+ if waiter is not None and not waiter.done():
177
+ waiter.set_exception(ApprovalCancelledError(request_id))
178
+ self._publish_event(ApprovalRuntimeEvent(kind="request_resolved", request=request))
179
+ self._publish_wire_response(request_id, "reject")
180
+ cancelled += 1
181
+ return cancelled
182
+
183
+ def list_pending(self) -> list[ApprovalRequestRecord]:
184
+ pending = [request for request in self._requests.values() if request.status == "pending"]
185
+ pending.sort(key=lambda request: request.created_at)
186
+ return pending
187
+
188
+ def get_request(self, request_id: str) -> ApprovalRequestRecord | None:
189
+ return self._requests.get(request_id)
190
+
191
+ def subscribe(self, callback: Callable[[ApprovalRuntimeEvent], None]) -> str:
192
+ token = uuid.uuid4().hex
193
+ self._subscribers[token] = callback
194
+ return token
195
+
196
+ def unsubscribe(self, token: str) -> None:
197
+ self._subscribers.pop(token, None)
198
+
199
+ def _publish_event(self, event: ApprovalRuntimeEvent) -> None:
200
+ for callback in list(self._subscribers.values()):
201
+ try:
202
+ callback(event)
203
+ except Exception:
204
+ logger.exception("Approval runtime event subscriber failed")
205
+
206
+ def _publish_wire_request(self, request: ApprovalRequestRecord) -> None:
207
+ if self._root_wire_hub is None:
208
+ return
209
+ self._root_wire_hub.publish_nowait(
210
+ ApprovalRequest(
211
+ id=request.id,
212
+ tool_call_id=request.tool_call_id,
213
+ sender=request.sender,
214
+ action=request.action,
215
+ description=request.description,
216
+ display=request.display,
217
+ source_kind=request.source.kind,
218
+ source_id=request.source.id,
219
+ agent_id=request.source.agent_id,
220
+ subagent_type=request.source.subagent_type,
221
+ )
222
+ )
223
+
224
+ def _publish_wire_response(
225
+ self, request_id: str, response: ApprovalResponseKind, feedback: str = ""
226
+ ) -> None:
227
+ if self._root_wire_hub is None:
228
+ return
229
+ self._root_wire_hub.publish_nowait(
230
+ ApprovalResponse(
231
+ request_id=request_id,
232
+ response=response,
233
+ feedback=feedback,
234
+ )
235
+ )
@@ -0,0 +1,5 @@
1
+ from __future__ import annotations
2
+
3
+ KIMI_CODE_PLATFORM_ID = "kimi-code"
4
+
5
+ __all__ = ["KIMI_CODE_PLATFORM_ID"]