windcode 0.1.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.
- windcode/__init__.py +6 -0
- windcode/__main__.py +4 -0
- windcode/auth/__init__.py +11 -0
- windcode/auth/store.py +90 -0
- windcode/cli.py +181 -0
- windcode/config/__init__.py +41 -0
- windcode/config/loader.py +117 -0
- windcode/config/models.py +203 -0
- windcode/config/writer.py +74 -0
- windcode/context/__init__.py +18 -0
- windcode/context/compactor.py +72 -0
- windcode/context/estimator.py +89 -0
- windcode/context/truncation.py +87 -0
- windcode/domain/__init__.py +1 -0
- windcode/domain/errors.py +33 -0
- windcode/domain/events.py +597 -0
- windcode/domain/messages.py +226 -0
- windcode/domain/models.py +73 -0
- windcode/domain/subagents.py +263 -0
- windcode/domain/tools.py +55 -0
- windcode/extensions/__init__.py +27 -0
- windcode/extensions/commands.py +25 -0
- windcode/extensions/discovery.py +139 -0
- windcode/extensions/events.py +58 -0
- windcode/extensions/hooks/__init__.py +4 -0
- windcode/extensions/hooks/dispatcher.py +107 -0
- windcode/extensions/hooks/executor.py +49 -0
- windcode/extensions/hooks/loader.py +101 -0
- windcode/extensions/hooks/models.py +109 -0
- windcode/extensions/mcp/__init__.py +10 -0
- windcode/extensions/mcp/adapter.py +101 -0
- windcode/extensions/mcp/catalog.py +153 -0
- windcode/extensions/mcp/client.py +273 -0
- windcode/extensions/mcp/runtime.py +144 -0
- windcode/extensions/mcp/tools.py +570 -0
- windcode/extensions/models.py +168 -0
- windcode/extensions/paths.py +71 -0
- windcode/extensions/plugins/__init__.py +3 -0
- windcode/extensions/plugins/installer.py +93 -0
- windcode/extensions/plugins/manifest.py +166 -0
- windcode/extensions/runtime.py +366 -0
- windcode/extensions/service.py +437 -0
- windcode/extensions/skills/__init__.py +3 -0
- windcode/extensions/skills/loader.py +67 -0
- windcode/extensions/skills/parser.py +57 -0
- windcode/extensions/skills/tools.py +213 -0
- windcode/extensions/snapshot.py +57 -0
- windcode/extensions/state.py +158 -0
- windcode/instructions/__init__.py +8 -0
- windcode/instructions/loader.py +50 -0
- windcode/memory/__init__.py +57 -0
- windcode/memory/extraction.py +83 -0
- windcode/memory/models.py +218 -0
- windcode/memory/refiner.py +189 -0
- windcode/memory/security.py +23 -0
- windcode/memory/service.py +179 -0
- windcode/memory/store.py +362 -0
- windcode/observability/__init__.py +4 -0
- windcode/observability/redaction.py +95 -0
- windcode/observability/trace.py +115 -0
- windcode/policy/__init__.py +22 -0
- windcode/policy/engine.py +137 -0
- windcode/policy/models.py +69 -0
- windcode/providers/__init__.py +29 -0
- windcode/providers/_utils.py +19 -0
- windcode/providers/anthropic.py +215 -0
- windcode/providers/base.py +46 -0
- windcode/providers/catalog.py +119 -0
- windcode/providers/errors.py +96 -0
- windcode/providers/openai_compat.py +231 -0
- windcode/providers/openai_responses.py +189 -0
- windcode/providers/registry.py +105 -0
- windcode/runtime/__init__.py +15 -0
- windcode/runtime/control.py +89 -0
- windcode/runtime/event_bus.py +67 -0
- windcode/runtime/loop.py +510 -0
- windcode/runtime/prompts.py +132 -0
- windcode/runtime/report.py +64 -0
- windcode/runtime/retry.py +73 -0
- windcode/runtime/scheduler.py +194 -0
- windcode/runtime/subagents/__init__.py +28 -0
- windcode/runtime/subagents/approvals.py +88 -0
- windcode/runtime/subagents/budgets.py +79 -0
- windcode/runtime/subagents/coordinator.py +714 -0
- windcode/runtime/subagents/factory.py +311 -0
- windcode/runtime/subagents/roles.py +74 -0
- windcode/runtime/subagents/verification.py +53 -0
- windcode/sandbox/__init__.py +3 -0
- windcode/sandbox/bwrap.py +79 -0
- windcode/sdk.py +1259 -0
- windcode/sessions/__init__.py +23 -0
- windcode/sessions/artifacts.py +69 -0
- windcode/sessions/models.py +104 -0
- windcode/sessions/store.py +167 -0
- windcode/sessions/tree.py +35 -0
- windcode/tools/__init__.py +10 -0
- windcode/tools/apply_patch.py +191 -0
- windcode/tools/ask_user.py +58 -0
- windcode/tools/builtins.py +44 -0
- windcode/tools/edit_file.py +54 -0
- windcode/tools/filesystem.py +77 -0
- windcode/tools/glob.py +35 -0
- windcode/tools/grep.py +66 -0
- windcode/tools/memory.py +400 -0
- windcode/tools/read_file.py +54 -0
- windcode/tools/registry.py +120 -0
- windcode/tools/shell.py +159 -0
- windcode/tools/subagents/__init__.py +31 -0
- windcode/tools/subagents/cancel.py +35 -0
- windcode/tools/subagents/integrate.py +54 -0
- windcode/tools/subagents/list.py +46 -0
- windcode/tools/subagents/spawn.py +86 -0
- windcode/tools/subagents/wait.py +74 -0
- windcode/tools/write_file.py +61 -0
- windcode/tui/__init__.py +3 -0
- windcode/tui/app.py +1015 -0
- windcode/tui/commands.py +90 -0
- windcode/tui/permission_display.py +24 -0
- windcode/tui/styles.tcss +591 -0
- windcode/tui/widgets/__init__.py +31 -0
- windcode/tui/widgets/approval.py +98 -0
- windcode/tui/widgets/command_menu.py +90 -0
- windcode/tui/widgets/extensions.py +48 -0
- windcode/tui/widgets/input.py +110 -0
- windcode/tui/widgets/memory.py +143 -0
- windcode/tui/widgets/messages.py +299 -0
- windcode/tui/widgets/models.py +565 -0
- windcode/tui/widgets/question.py +46 -0
- windcode/tui/widgets/sessions.py +68 -0
- windcode/tui/widgets/status.py +46 -0
- windcode/tui/widgets/subagents.py +195 -0
- windcode/tui/widgets/tools.py +66 -0
- windcode/tui/widgets/welcome.py +149 -0
- windcode/types.py +80 -0
- windcode/worktrees/__init__.py +24 -0
- windcode/worktrees/git.py +92 -0
- windcode/worktrees/manager.py +265 -0
- windcode/worktrees/models.py +62 -0
- windcode-0.1.0.dist-info/METADATA +207 -0
- windcode-0.1.0.dist-info/RECORD +143 -0
- windcode-0.1.0.dist-info/WHEEL +4 -0
- windcode-0.1.0.dist-info/entry_points.txt +2 -0
- windcode-0.1.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import os
|
|
5
|
+
from collections.abc import Awaitable, Callable
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from windcode.auth import CredentialStore
|
|
10
|
+
from windcode.config.models import (
|
|
11
|
+
EnvironmentReference,
|
|
12
|
+
McpHttpConfig,
|
|
13
|
+
McpStdioConfig,
|
|
14
|
+
SecretReference,
|
|
15
|
+
)
|
|
16
|
+
from windcode.domain.events import (
|
|
17
|
+
ExtensionEvent,
|
|
18
|
+
SubagentCancelled,
|
|
19
|
+
SubagentCompleted,
|
|
20
|
+
SubagentEvent,
|
|
21
|
+
SubagentFailed,
|
|
22
|
+
SubagentStarted,
|
|
23
|
+
)
|
|
24
|
+
from windcode.domain.messages import SourcedContextMessage
|
|
25
|
+
from windcode.domain.tools import ToolContext, ToolResult
|
|
26
|
+
from windcode.extensions.events import extension_event
|
|
27
|
+
from windcode.extensions.hooks.dispatcher import HookDispatcher
|
|
28
|
+
from windcode.extensions.hooks.executor import HookExecutor
|
|
29
|
+
from windcode.extensions.hooks.models import HookContext, HookDefinition, HookEvent
|
|
30
|
+
from windcode.extensions.mcp.catalog import McpToolDefinition
|
|
31
|
+
from windcode.extensions.mcp.client import McpClient, ResolvedHttpServer, ResolvedStdioServer
|
|
32
|
+
from windcode.extensions.mcp.runtime import McpRuntime
|
|
33
|
+
from windcode.extensions.mcp.tools import McpCapabilityService
|
|
34
|
+
from windcode.extensions.models import CapabilityKind, ExtensionSnapshot
|
|
35
|
+
from windcode.extensions.skills.loader import SkillLoader
|
|
36
|
+
from windcode.extensions.skills.tools import SkillActivationResult, SkillCatalog, SkillRuntime
|
|
37
|
+
from windcode.policy.models import PolicyDecision, PolicyRequest
|
|
38
|
+
from windcode.runtime.scheduler import PolicyConstraints, ScheduledCall
|
|
39
|
+
from windcode.sessions.artifacts import ArtifactStore
|
|
40
|
+
|
|
41
|
+
SecretObserver = Callable[[str], None]
|
|
42
|
+
ExtensionEventObserver = Callable[[ExtensionEvent], Awaitable[object]]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _resolve_reference(
|
|
46
|
+
reference: SecretReference,
|
|
47
|
+
credential_store: CredentialStore,
|
|
48
|
+
observe_secret: SecretObserver | None,
|
|
49
|
+
) -> str:
|
|
50
|
+
if isinstance(reference, EnvironmentReference):
|
|
51
|
+
value = os.environ.get(reference.env)
|
|
52
|
+
label = f"environment variable {reference.env}"
|
|
53
|
+
else:
|
|
54
|
+
value = credential_store.get(reference.credential)
|
|
55
|
+
label = f"credential {reference.credential}"
|
|
56
|
+
if value is None:
|
|
57
|
+
raise ValueError(f"missing MCP secret reference: {label}")
|
|
58
|
+
if observe_secret is not None:
|
|
59
|
+
observe_secret(value)
|
|
60
|
+
return value
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass(slots=True)
|
|
64
|
+
class RunExtensions:
|
|
65
|
+
snapshot: ExtensionSnapshot
|
|
66
|
+
session_id: str
|
|
67
|
+
run_id: str
|
|
68
|
+
skills: SkillRuntime
|
|
69
|
+
mcp: McpRuntime
|
|
70
|
+
mcp_capabilities: McpCapabilityService
|
|
71
|
+
hooks: HookDispatcher
|
|
72
|
+
event_observer: ExtensionEventObserver | None = None
|
|
73
|
+
owns_mcp: bool = True
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def create(
|
|
77
|
+
cls,
|
|
78
|
+
snapshot: ExtensionSnapshot,
|
|
79
|
+
*,
|
|
80
|
+
session_id: str,
|
|
81
|
+
run_id: str,
|
|
82
|
+
credential_store: CredentialStore,
|
|
83
|
+
max_content_bytes: int,
|
|
84
|
+
connect_timeout: float,
|
|
85
|
+
call_timeout: float,
|
|
86
|
+
observe_secret: SecretObserver | None = None,
|
|
87
|
+
artifact_store: ArtifactStore | None = None,
|
|
88
|
+
network_enabled: bool = False,
|
|
89
|
+
mcp_runtime: McpRuntime | None = None,
|
|
90
|
+
mcp_tool_catalogs: dict[str, tuple[McpToolDefinition, ...]] | None = None,
|
|
91
|
+
) -> RunExtensions:
|
|
92
|
+
servers: dict[str, tuple[Callable[[], McpClient], bool]] = {}
|
|
93
|
+
hooks: list[HookDefinition] = []
|
|
94
|
+
records = {record.capability_id: record for record in snapshot.capabilities}
|
|
95
|
+
for stable_id, definition in snapshot.definitions.items():
|
|
96
|
+
record = records.get(stable_id)
|
|
97
|
+
if record is None or not record.enabled or not record.trusted:
|
|
98
|
+
continue
|
|
99
|
+
if record.kind is CapabilityKind.HOOK and isinstance(definition, HookDefinition):
|
|
100
|
+
hooks.append(definition)
|
|
101
|
+
elif record.kind is CapabilityKind.MCP_SERVER and isinstance(
|
|
102
|
+
definition, (McpStdioConfig, McpHttpConfig)
|
|
103
|
+
):
|
|
104
|
+
if isinstance(definition, McpStdioConfig):
|
|
105
|
+
|
|
106
|
+
def stdio_factory(
|
|
107
|
+
server: McpStdioConfig = definition,
|
|
108
|
+
source_path: Path | None = record.source.path,
|
|
109
|
+
) -> McpClient:
|
|
110
|
+
environment = {
|
|
111
|
+
key: _resolve_reference(value, credential_store, observe_secret)
|
|
112
|
+
for key, value in server.env.items()
|
|
113
|
+
}
|
|
114
|
+
cwd = (
|
|
115
|
+
None
|
|
116
|
+
if server.cwd is None or source_path is None
|
|
117
|
+
else source_path / server.cwd
|
|
118
|
+
)
|
|
119
|
+
return McpClient(
|
|
120
|
+
ResolvedStdioServer(server.command, server.args, cwd, environment),
|
|
121
|
+
connect_timeout=connect_timeout,
|
|
122
|
+
call_timeout=call_timeout,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
client_factory = stdio_factory
|
|
126
|
+
else:
|
|
127
|
+
|
|
128
|
+
def http_factory(server: McpHttpConfig = definition) -> McpClient:
|
|
129
|
+
if not network_enabled:
|
|
130
|
+
raise PermissionError("MCP HTTP is disabled by the run network policy")
|
|
131
|
+
headers = {
|
|
132
|
+
key: _resolve_reference(value, credential_store, observe_secret)
|
|
133
|
+
for key, value in server.headers.items()
|
|
134
|
+
}
|
|
135
|
+
return McpClient(
|
|
136
|
+
ResolvedHttpServer(server.url, headers),
|
|
137
|
+
connect_timeout=connect_timeout,
|
|
138
|
+
call_timeout=call_timeout,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
client_factory = http_factory
|
|
142
|
+
|
|
143
|
+
servers[record.public_name] = (client_factory, definition.required)
|
|
144
|
+
runtime = mcp_runtime or McpRuntime(servers)
|
|
145
|
+
run_extensions = cls(
|
|
146
|
+
snapshot,
|
|
147
|
+
session_id,
|
|
148
|
+
run_id,
|
|
149
|
+
SkillRuntime(SkillCatalog(snapshot, SkillLoader(max_content_bytes=max_content_bytes))),
|
|
150
|
+
runtime,
|
|
151
|
+
McpCapabilityService(
|
|
152
|
+
runtime,
|
|
153
|
+
artifact_store=artifact_store,
|
|
154
|
+
content_limit=max_content_bytes,
|
|
155
|
+
tool_catalogs=mcp_tool_catalogs,
|
|
156
|
+
),
|
|
157
|
+
HookDispatcher(tuple(hooks), HookExecutor()),
|
|
158
|
+
owns_mcp=mcp_runtime is None,
|
|
159
|
+
)
|
|
160
|
+
run_extensions.hooks.observer = run_extensions.observe_hook
|
|
161
|
+
run_extensions.mcp.observer = run_extensions.observe_mcp
|
|
162
|
+
return run_extensions
|
|
163
|
+
|
|
164
|
+
def _context(
|
|
165
|
+
self,
|
|
166
|
+
event: HookEvent,
|
|
167
|
+
correlation_id: str,
|
|
168
|
+
*,
|
|
169
|
+
tool_id: str | None = None,
|
|
170
|
+
status: str | None = None,
|
|
171
|
+
source_id: str = "windcode",
|
|
172
|
+
) -> HookContext:
|
|
173
|
+
return HookContext(
|
|
174
|
+
1,
|
|
175
|
+
event,
|
|
176
|
+
self.session_id,
|
|
177
|
+
self.run_id,
|
|
178
|
+
correlation_id,
|
|
179
|
+
source_id=source_id,
|
|
180
|
+
tool_id=tool_id,
|
|
181
|
+
status=status,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
async def _emit(
|
|
185
|
+
self,
|
|
186
|
+
action: str,
|
|
187
|
+
*,
|
|
188
|
+
extension_id: str,
|
|
189
|
+
source_id: str,
|
|
190
|
+
status: str = "",
|
|
191
|
+
hook_id: str | None = None,
|
|
192
|
+
server_id: str | None = None,
|
|
193
|
+
call_id: str | None = None,
|
|
194
|
+
) -> None:
|
|
195
|
+
if self.event_observer is None:
|
|
196
|
+
return
|
|
197
|
+
from uuid import uuid4
|
|
198
|
+
|
|
199
|
+
await self.event_observer(
|
|
200
|
+
extension_event(
|
|
201
|
+
event_id=uuid4().hex,
|
|
202
|
+
session_id=self.session_id,
|
|
203
|
+
run_id=self.run_id,
|
|
204
|
+
turn=0,
|
|
205
|
+
action=action,
|
|
206
|
+
snapshot_generation=self.snapshot.generation,
|
|
207
|
+
extension_id=extension_id,
|
|
208
|
+
source_id=source_id,
|
|
209
|
+
status=status,
|
|
210
|
+
hook_id=hook_id,
|
|
211
|
+
server_id=server_id,
|
|
212
|
+
call_id=call_id,
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
async def observe_hook(
|
|
217
|
+
self,
|
|
218
|
+
phase: str,
|
|
219
|
+
hook: HookDefinition,
|
|
220
|
+
context: HookContext,
|
|
221
|
+
outcome: object,
|
|
222
|
+
) -> None:
|
|
223
|
+
del outcome
|
|
224
|
+
action = {
|
|
225
|
+
"started": "hook_started",
|
|
226
|
+
"finished": "hook_finished",
|
|
227
|
+
"rejected": "hook_rejected",
|
|
228
|
+
}[phase]
|
|
229
|
+
await self._emit(
|
|
230
|
+
action,
|
|
231
|
+
extension_id=hook.source_id,
|
|
232
|
+
source_id=hook.source_id,
|
|
233
|
+
status=phase,
|
|
234
|
+
hook_id=hook.hook_id,
|
|
235
|
+
call_id=context.correlation_id,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
async def observe_mcp(self, action: str, server_id: str, status: str) -> None:
|
|
239
|
+
await self._emit(
|
|
240
|
+
action,
|
|
241
|
+
extension_id=f"mcp_server:{server_id}",
|
|
242
|
+
source_id=f"mcp:{server_id}",
|
|
243
|
+
status=status,
|
|
244
|
+
server_id=server_id,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
async def before_policy(self, call: ScheduledCall, context: ToolContext) -> PolicyConstraints:
|
|
248
|
+
del context
|
|
249
|
+
outcome = await self.hooks.dispatch(
|
|
250
|
+
self._context(
|
|
251
|
+
HookEvent.TOOL_BEFORE_POLICY,
|
|
252
|
+
call.call_id,
|
|
253
|
+
tool_id=call.tool_name,
|
|
254
|
+
source_id=call.origin or "windcode",
|
|
255
|
+
)
|
|
256
|
+
)
|
|
257
|
+
return PolicyConstraints(outcome.additional_effects, outcome.rejected)
|
|
258
|
+
|
|
259
|
+
async def permission_requested(
|
|
260
|
+
self, call: ScheduledCall, request: PolicyRequest, decision: PolicyDecision
|
|
261
|
+
) -> None:
|
|
262
|
+
del request
|
|
263
|
+
await self.hooks.dispatch(
|
|
264
|
+
self._context(
|
|
265
|
+
HookEvent.PERMISSION_REQUEST,
|
|
266
|
+
call.call_id,
|
|
267
|
+
tool_id=call.tool_name,
|
|
268
|
+
status=decision.action.value,
|
|
269
|
+
source_id=call.origin or "windcode",
|
|
270
|
+
)
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
async def after_execute(
|
|
274
|
+
self, call: ScheduledCall, request: PolicyRequest, result: ToolResult
|
|
275
|
+
) -> None:
|
|
276
|
+
del request
|
|
277
|
+
await self.hooks.dispatch(
|
|
278
|
+
self._context(
|
|
279
|
+
HookEvent.TOOL_AFTER,
|
|
280
|
+
call.call_id,
|
|
281
|
+
tool_id=call.tool_name,
|
|
282
|
+
status="error" if result.is_error else "success",
|
|
283
|
+
source_id=call.origin or "windcode",
|
|
284
|
+
),
|
|
285
|
+
background=True,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
async def lifecycle(self, event: HookEvent, *, status: str | None = None) -> None:
|
|
289
|
+
await self.hooks.dispatch(
|
|
290
|
+
self._context(event, self.run_id, status=status),
|
|
291
|
+
background=event in {HookEvent.RUN_END, HookEvent.SESSION_END},
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
async def compact_lifecycle(self, phase: str) -> None:
|
|
295
|
+
event = HookEvent.COMPACT_BEFORE if phase == "before" else HookEvent.COMPACT_AFTER
|
|
296
|
+
await self.lifecycle(event, status=phase)
|
|
297
|
+
|
|
298
|
+
async def subagent_lifecycle(self, event: SubagentEvent) -> None:
|
|
299
|
+
if isinstance(event, SubagentStarted):
|
|
300
|
+
hook_event = HookEvent.SUBAGENT_START
|
|
301
|
+
elif isinstance(event, (SubagentCompleted, SubagentFailed, SubagentCancelled)):
|
|
302
|
+
hook_event = HookEvent.SUBAGENT_END
|
|
303
|
+
else:
|
|
304
|
+
return
|
|
305
|
+
await self.hooks.dispatch(
|
|
306
|
+
HookContext(
|
|
307
|
+
1,
|
|
308
|
+
hook_event,
|
|
309
|
+
self.session_id,
|
|
310
|
+
self.run_id,
|
|
311
|
+
event.subagent_id,
|
|
312
|
+
status=event.kind,
|
|
313
|
+
fields=(
|
|
314
|
+
("subagent_id", event.subagent_id),
|
|
315
|
+
("role", event.role),
|
|
316
|
+
("task_name", event.task_name),
|
|
317
|
+
),
|
|
318
|
+
)
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
async def activate_skill(self, selector: str) -> SkillActivationResult:
|
|
322
|
+
result = self.skills.activate(selector)
|
|
323
|
+
await self._emit(
|
|
324
|
+
"skill_loaded",
|
|
325
|
+
extension_id=result.name,
|
|
326
|
+
source_id=result.source_id,
|
|
327
|
+
status="loaded" if result.loaded else "already_loaded",
|
|
328
|
+
)
|
|
329
|
+
return result
|
|
330
|
+
|
|
331
|
+
async def activate_prompt(self, selector: str) -> None:
|
|
332
|
+
await self.mcp_capabilities.activate_prompt(selector)
|
|
333
|
+
|
|
334
|
+
def activate_capability(self, selector: str, *, source_id: str = "plugin-command") -> None:
|
|
335
|
+
matches = [
|
|
336
|
+
record
|
|
337
|
+
for record in self.snapshot.capabilities
|
|
338
|
+
if record.enabled
|
|
339
|
+
and record.trusted
|
|
340
|
+
and (record.capability_id == selector or record.public_name == selector)
|
|
341
|
+
]
|
|
342
|
+
if not matches:
|
|
343
|
+
raise KeyError(f"unknown extension capability: {selector}")
|
|
344
|
+
if len(matches) > 1:
|
|
345
|
+
raise ValueError(f"ambiguous extension capability: {selector}")
|
|
346
|
+
record = matches[0]
|
|
347
|
+
self.hooks.executor.context_messages.append(
|
|
348
|
+
SourcedContextMessage(
|
|
349
|
+
source_id,
|
|
350
|
+
f"The user selected extension capability {record.capability_id}. "
|
|
351
|
+
"Use it through the normal tool and permission workflow.",
|
|
352
|
+
)
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
def drain_context(self) -> tuple[SourcedContextMessage, ...]:
|
|
356
|
+
return (
|
|
357
|
+
*self.skills.drain_context(),
|
|
358
|
+
*self.hooks.executor.drain_context(),
|
|
359
|
+
*self.mcp_capabilities.drain_context(),
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
async def aclose(self) -> None:
|
|
363
|
+
pending = [self.hooks.aclose()]
|
|
364
|
+
if self.owns_mcp:
|
|
365
|
+
pending.append(self.mcp.aclose())
|
|
366
|
+
await asyncio.gather(*pending, return_exceptions=True)
|