stagent 0.10.0 → 0.11.1
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.
- package/README.md +44 -31
- package/dist/cli.js +24 -0
- package/docs/.coverage-gaps.json +154 -24
- package/docs/.last-generated +1 -1
- package/docs/features/agent-intelligence.md +12 -2
- package/docs/features/chat.md +40 -5
- package/docs/features/cost-usage.md +1 -1
- package/docs/features/documents.md +5 -2
- package/docs/features/inbox-notifications.md +10 -2
- package/docs/features/keyboard-navigation.md +12 -3
- package/docs/features/provider-runtimes.md +16 -2
- package/docs/features/settings.md +2 -2
- package/docs/features/shared-components.md +7 -3
- package/docs/features/tables.md +3 -1
- package/docs/features/tool-permissions.md +6 -2
- package/docs/features/workflows.md +6 -2
- package/docs/getting-started.md +1 -1
- package/docs/index.md +1 -1
- package/docs/journeys/developer.md +25 -2
- package/docs/journeys/personal-use.md +12 -5
- package/docs/journeys/power-user.md +45 -14
- package/docs/journeys/work-use.md +17 -8
- package/docs/manifest.json +15 -15
- package/docs/superpowers/plans/2026-04-07-instance-bootstrap.md +2 -2
- package/docs/superpowers/plans/2026-04-14-chat-command-namespace-refactor.md +1390 -0
- package/docs/superpowers/plans/2026-04-14-chat-environment-integration.md +1561 -0
- package/docs/superpowers/plans/2026-04-14-chat-polish-bundle-v1.md +1219 -0
- package/docs/superpowers/plans/2026-04-14-chat-session-persistence-provider-closeout.md +399 -0
- package/next.config.mjs +1 -0
- package/package.json +3 -3
- package/src/app/api/chat/conversations/[id]/skills/__tests__/activate.test.ts +141 -0
- package/src/app/api/chat/conversations/[id]/skills/activate/route.ts +74 -0
- package/src/app/api/chat/conversations/[id]/skills/deactivate/route.ts +33 -0
- package/src/app/api/chat/export/route.ts +52 -0
- package/src/app/api/chat/files/search/route.ts +50 -0
- package/src/app/api/environment/rescan-if-stale/__tests__/route.test.ts +45 -0
- package/src/app/api/environment/rescan-if-stale/route.ts +23 -0
- package/src/app/api/environment/skills/route.ts +13 -0
- package/src/app/api/schedules/[id]/execute/route.ts +2 -2
- package/src/app/api/settings/chat/pins/route.ts +94 -0
- package/src/app/api/settings/chat/saved-searches/__tests__/route.test.ts +119 -0
- package/src/app/api/settings/chat/saved-searches/route.ts +79 -0
- package/src/app/api/settings/environment/route.ts +26 -0
- package/src/app/api/tasks/[id]/execute/route.ts +52 -12
- package/src/app/api/tasks/[id]/respond/route.ts +31 -15
- package/src/app/api/tasks/[id]/resume/route.ts +24 -3
- package/src/app/documents/page.tsx +4 -1
- package/src/app/settings/page.tsx +2 -0
- package/src/components/book/content-blocks.tsx +1 -1
- package/src/components/chat/__tests__/capability-banner.test.tsx +38 -0
- package/src/components/chat/__tests__/chat-session-provider.test.tsx +166 -1
- package/src/components/chat/__tests__/skill-row.test.tsx +91 -0
- package/src/components/chat/capability-banner.tsx +68 -0
- package/src/components/chat/chat-command-popover.tsx +668 -47
- package/src/components/chat/chat-input.tsx +103 -8
- package/src/components/chat/chat-message.tsx +12 -3
- package/src/components/chat/chat-session-provider.tsx +73 -3
- package/src/components/chat/chat-shell.tsx +62 -3
- package/src/components/chat/command-tab-bar.tsx +68 -0
- package/src/components/chat/conversation-template-picker.tsx +421 -0
- package/src/components/chat/help-dialog.tsx +39 -0
- package/src/components/chat/skill-composition-conflict-dialog.tsx +96 -0
- package/src/components/chat/skill-row.tsx +147 -0
- package/src/components/documents/document-browser.tsx +37 -19
- package/src/components/notifications/__tests__/permission-response-actions.test.tsx +70 -0
- package/src/components/notifications/permission-response-actions.tsx +155 -1
- package/src/components/playbook/playbook-detail-view.tsx +1 -1
- package/src/components/settings/environment-section.tsx +102 -0
- package/src/components/shared/__tests__/filter-hint.test.tsx +40 -0
- package/src/components/shared/__tests__/saved-searches-manager.test.tsx +147 -0
- package/src/components/shared/command-palette.tsx +262 -2
- package/src/components/shared/filter-hint.tsx +70 -0
- package/src/components/shared/filter-input.tsx +59 -0
- package/src/components/shared/saved-searches-manager.tsx +199 -0
- package/src/components/tasks/task-bento-grid.tsx +12 -2
- package/src/components/tasks/task-card.tsx +3 -0
- package/src/components/tasks/task-chip-bar.tsx +30 -1
- package/src/hooks/__tests__/use-chat-autocomplete-tabs.test.ts +47 -0
- package/src/hooks/__tests__/use-saved-searches.test.ts +70 -0
- package/src/hooks/use-active-skills.ts +110 -0
- package/src/hooks/use-chat-autocomplete.ts +120 -7
- package/src/hooks/use-enriched-skills.ts +19 -0
- package/src/hooks/use-pinned-entries.ts +104 -0
- package/src/hooks/use-recent-user-messages.ts +19 -0
- package/src/hooks/use-saved-searches.ts +142 -0
- package/src/lib/agents/__tests__/claude-agent-sdk-options.test.ts +56 -0
- package/src/lib/agents/__tests__/claude-agent.test.ts +17 -4
- package/src/lib/agents/__tests__/task-dispatch.test.ts +166 -0
- package/src/lib/agents/__tests__/tool-permissions.test.ts +60 -0
- package/src/lib/agents/claude-agent.ts +105 -46
- package/src/lib/agents/handoff/bus.ts +2 -2
- package/src/lib/agents/profiles/__tests__/list-fused-profiles.test.ts +110 -0
- package/src/lib/agents/profiles/__tests__/registry.test.ts +47 -0
- package/src/lib/agents/profiles/builtins/upgrade-assistant/SKILL.md +30 -3
- package/src/lib/agents/profiles/builtins/upgrade-assistant/profile.yaml +6 -2
- package/src/lib/agents/profiles/list-fused-profiles.ts +104 -0
- package/src/lib/agents/profiles/registry.ts +97 -22
- package/src/lib/agents/profiles/types.ts +7 -1
- package/src/lib/agents/router.ts +3 -6
- package/src/lib/agents/runtime/__tests__/catalog.test.ts +130 -0
- package/src/lib/agents/runtime/__tests__/execution-target.test.ts +183 -0
- package/src/lib/agents/runtime/anthropic-direct.ts +8 -0
- package/src/lib/agents/runtime/catalog.ts +121 -0
- package/src/lib/agents/runtime/claude-sdk.ts +32 -0
- package/src/lib/agents/runtime/execution-target.ts +456 -0
- package/src/lib/agents/runtime/index.ts +4 -0
- package/src/lib/agents/runtime/launch-failure.ts +101 -0
- package/src/lib/agents/runtime/openai-codex.ts +35 -0
- package/src/lib/agents/runtime/openai-direct.ts +8 -0
- package/src/lib/agents/task-dispatch.ts +220 -0
- package/src/lib/agents/tool-permissions.ts +16 -1
- package/src/lib/chat/__tests__/active-skill-injection.test.ts +261 -0
- package/src/lib/chat/__tests__/clean-filter-input.test.ts +68 -0
- package/src/lib/chat/__tests__/command-tabs.test.ts +68 -0
- package/src/lib/chat/__tests__/context-builder-files.test.ts +112 -0
- package/src/lib/chat/__tests__/dismissals.test.ts +65 -0
- package/src/lib/chat/__tests__/engine-sdk-options.test.ts +117 -0
- package/src/lib/chat/__tests__/skill-conflict.test.ts +35 -0
- package/src/lib/chat/__tests__/types.test.ts +28 -0
- package/src/lib/chat/active-skills.ts +31 -0
- package/src/lib/chat/clean-filter-input.ts +30 -0
- package/src/lib/chat/codex-engine.ts +30 -7
- package/src/lib/chat/command-tabs.ts +61 -0
- package/src/lib/chat/context-builder.ts +141 -1
- package/src/lib/chat/dismissals.ts +73 -0
- package/src/lib/chat/engine.ts +109 -15
- package/src/lib/chat/files/__tests__/search.test.ts +135 -0
- package/src/lib/chat/files/expand-mention.ts +76 -0
- package/src/lib/chat/files/search.ts +99 -0
- package/src/lib/chat/skill-composition.ts +210 -0
- package/src/lib/chat/skill-conflict.ts +105 -0
- package/src/lib/chat/stagent-tools.ts +6 -19
- package/src/lib/chat/stream-telemetry.ts +9 -4
- package/src/lib/chat/system-prompt.ts +22 -0
- package/src/lib/chat/tool-catalog.ts +33 -3
- package/src/lib/chat/tools/__tests__/profile-tools.test.ts +51 -0
- package/src/lib/chat/tools/__tests__/settings-tools.test.ts +294 -0
- package/src/lib/chat/tools/__tests__/skill-tools.test.ts +474 -0
- package/src/lib/chat/tools/__tests__/task-tools.test.ts +47 -0
- package/src/lib/chat/tools/__tests__/workflow-tools-dedup.test.ts +134 -0
- package/src/lib/chat/tools/blueprint-tools.ts +190 -0
- package/src/lib/chat/tools/helpers.ts +2 -0
- package/src/lib/chat/tools/profile-tools.ts +120 -23
- package/src/lib/chat/tools/skill-tools.ts +183 -0
- package/src/lib/chat/tools/task-tools.ts +6 -2
- package/src/lib/chat/tools/workflow-tools.ts +61 -20
- package/src/lib/chat/types.ts +15 -0
- package/src/lib/constants/settings.ts +2 -0
- package/src/lib/data/clear.ts +2 -6
- package/src/lib/db/bootstrap.ts +17 -0
- package/src/lib/db/schema.ts +26 -0
- package/src/lib/environment/__tests__/auto-promote.test.ts +132 -0
- package/src/lib/environment/__tests__/list-skills-enriched.test.ts +55 -0
- package/src/lib/environment/__tests__/skill-enrichment.test.ts +129 -0
- package/src/lib/environment/__tests__/skill-recommendations.test.ts +87 -0
- package/src/lib/environment/data.ts +9 -0
- package/src/lib/environment/list-skills.ts +176 -0
- package/src/lib/environment/parsers/__tests__/skill.test.ts +54 -0
- package/src/lib/environment/parsers/skill.ts +26 -5
- package/src/lib/environment/profile-generator.ts +56 -2
- package/src/lib/environment/skill-enrichment.ts +106 -0
- package/src/lib/environment/skill-recommendations.ts +66 -0
- package/src/lib/filters/__tests__/parse.quoted.test.ts +40 -0
- package/src/lib/filters/__tests__/parse.test.ts +135 -0
- package/src/lib/filters/parse.ts +86 -0
- package/src/lib/instance/__tests__/detect.test.ts +1 -1
- package/src/lib/instance/__tests__/upgrade-poller.test.ts +50 -0
- package/src/lib/instance/fingerprint.ts +8 -10
- package/src/lib/instance/upgrade-poller.ts +53 -1
- package/src/lib/schedules/scheduler.ts +4 -4
- package/src/lib/utils/stagent-paths.ts +4 -0
- package/src/lib/workflows/blueprints/__tests__/render-prompt.test.ts +124 -0
- package/src/lib/workflows/blueprints/render-prompt.ts +71 -0
- package/src/lib/workflows/blueprints/types.ts +6 -0
- package/src/lib/workflows/engine.ts +5 -3
- package/src/test/setup.ts +10 -0
|
@@ -21,6 +21,51 @@ export interface RuntimeCapabilities {
|
|
|
21
21
|
authHealthCheck: boolean;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* LLM-surface features that affect what the model sees and which tools/skills
|
|
26
|
+
* Stagent exposes to it. Distinct from RuntimeCapabilities above, which is
|
|
27
|
+
* adapter-plumbing concerns (can the adapter resume/cancel/etc.).
|
|
28
|
+
*
|
|
29
|
+
* Values reflect post-Phase-1 capability (what the runtime SDK *can* do),
|
|
30
|
+
* not current engagement (what `engine.ts` currently activates). Downstream
|
|
31
|
+
* features read this bag to decide rendering, filtering, and dispatch.
|
|
32
|
+
*/
|
|
33
|
+
export interface RuntimeFeatures {
|
|
34
|
+
/** SDK provides a native skill-invocation tool (e.g. Claude SDK `Skill` tool). */
|
|
35
|
+
hasNativeSkills: boolean;
|
|
36
|
+
/** SDK loads skill metadata first, full SKILL.md on demand. */
|
|
37
|
+
hasProgressiveDisclosure: boolean;
|
|
38
|
+
/** Read/Grep/Glob/Edit/Write available as LLM tools. */
|
|
39
|
+
hasFilesystemTools: boolean;
|
|
40
|
+
/** Bash tool available (Stagent gates via permission bridge). */
|
|
41
|
+
hasBash: boolean;
|
|
42
|
+
/** TodoWrite tool available. */
|
|
43
|
+
hasTodoWrite: boolean;
|
|
44
|
+
/** Runtime supports delegating to sub-agents (e.g. Task tool). */
|
|
45
|
+
hasSubagentDelegation: boolean;
|
|
46
|
+
/** Runtime loads filesystem hooks (pre/post tool-use shell scripts). */
|
|
47
|
+
hasHooks: boolean;
|
|
48
|
+
/** Which project-level instructions file the runtime auto-loads, if any. */
|
|
49
|
+
autoLoadsInstructions: "CLAUDE.md" | "AGENTS.md" | null;
|
|
50
|
+
/**
|
|
51
|
+
* Runtime has no native skill support — Stagent must inject SKILL.md content
|
|
52
|
+
* into the system prompt to expose skills to the LLM.
|
|
53
|
+
*/
|
|
54
|
+
stagentInjectsSkills: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Runtime supports composing multiple active skills in one conversation.
|
|
57
|
+
* When false, only one skill may be active at a time (Ollama: context
|
|
58
|
+
* budget too tight). When true, `activate_skill mode:"add"` is allowed
|
|
59
|
+
* up to `maxActiveSkills`.
|
|
60
|
+
*/
|
|
61
|
+
supportsSkillComposition: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Maximum number of skills that may be simultaneously active. Enforced
|
|
64
|
+
* by the activate_skill tool. Ignored when supportsSkillComposition=false.
|
|
65
|
+
*/
|
|
66
|
+
maxActiveSkills: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
24
69
|
export interface RuntimeModelConfig {
|
|
25
70
|
/** Default model ID for this runtime */
|
|
26
71
|
default: string;
|
|
@@ -34,6 +79,7 @@ export interface RuntimeCatalogEntry {
|
|
|
34
79
|
description: string;
|
|
35
80
|
providerId: "anthropic" | "openai" | "ollama";
|
|
36
81
|
capabilities: RuntimeCapabilities;
|
|
82
|
+
features: RuntimeFeatures;
|
|
37
83
|
/** Model catalog — default and supported model IDs for this runtime */
|
|
38
84
|
models: RuntimeModelConfig;
|
|
39
85
|
}
|
|
@@ -54,6 +100,19 @@ const RUNTIME_CATALOG: Record<AgentRuntimeId, RuntimeCatalogEntry> = {
|
|
|
54
100
|
profileAssist: true,
|
|
55
101
|
authHealthCheck: true,
|
|
56
102
|
},
|
|
103
|
+
features: {
|
|
104
|
+
hasNativeSkills: true,
|
|
105
|
+
hasProgressiveDisclosure: true,
|
|
106
|
+
hasFilesystemTools: true,
|
|
107
|
+
hasBash: true,
|
|
108
|
+
hasTodoWrite: true,
|
|
109
|
+
hasSubagentDelegation: false, // Stagent task primitives replace SDK Task tool
|
|
110
|
+
hasHooks: false, // excluded per Q2
|
|
111
|
+
autoLoadsInstructions: "CLAUDE.md",
|
|
112
|
+
stagentInjectsSkills: false,
|
|
113
|
+
supportsSkillComposition: true,
|
|
114
|
+
maxActiveSkills: 3,
|
|
115
|
+
},
|
|
57
116
|
models: {
|
|
58
117
|
default: "sonnet",
|
|
59
118
|
supported: ["haiku", "sonnet", "opus"],
|
|
@@ -74,6 +133,19 @@ const RUNTIME_CATALOG: Record<AgentRuntimeId, RuntimeCatalogEntry> = {
|
|
|
74
133
|
profileAssist: false,
|
|
75
134
|
authHealthCheck: true,
|
|
76
135
|
},
|
|
136
|
+
features: {
|
|
137
|
+
hasNativeSkills: true,
|
|
138
|
+
hasProgressiveDisclosure: true,
|
|
139
|
+
hasFilesystemTools: true,
|
|
140
|
+
hasBash: true,
|
|
141
|
+
hasTodoWrite: true,
|
|
142
|
+
hasSubagentDelegation: false,
|
|
143
|
+
hasHooks: false,
|
|
144
|
+
autoLoadsInstructions: "AGENTS.md",
|
|
145
|
+
stagentInjectsSkills: false,
|
|
146
|
+
supportsSkillComposition: true,
|
|
147
|
+
maxActiveSkills: 3,
|
|
148
|
+
},
|
|
77
149
|
models: {
|
|
78
150
|
default: "gpt-5.4",
|
|
79
151
|
supported: ["gpt-5.4", "gpt-5.4-mini", "gpt-5.3-codex"],
|
|
@@ -94,6 +166,21 @@ const RUNTIME_CATALOG: Record<AgentRuntimeId, RuntimeCatalogEntry> = {
|
|
|
94
166
|
profileAssist: true,
|
|
95
167
|
authHealthCheck: true,
|
|
96
168
|
},
|
|
169
|
+
features: {
|
|
170
|
+
// Direct Messages API — no SDK-native skill machinery.
|
|
171
|
+
// Revisit when chat-claude-sdk-skills designs direct-API skill injection.
|
|
172
|
+
hasNativeSkills: false,
|
|
173
|
+
hasProgressiveDisclosure: false,
|
|
174
|
+
hasFilesystemTools: false,
|
|
175
|
+
hasBash: false,
|
|
176
|
+
hasTodoWrite: false,
|
|
177
|
+
hasSubagentDelegation: false,
|
|
178
|
+
hasHooks: false,
|
|
179
|
+
autoLoadsInstructions: null,
|
|
180
|
+
stagentInjectsSkills: false,
|
|
181
|
+
supportsSkillComposition: true,
|
|
182
|
+
maxActiveSkills: 3,
|
|
183
|
+
},
|
|
97
184
|
models: {
|
|
98
185
|
default: "claude-sonnet-4-20250514",
|
|
99
186
|
supported: ["claude-haiku-4-5-20251001", "claude-sonnet-4-20250514", "claude-opus-4-20250514"],
|
|
@@ -114,6 +201,21 @@ const RUNTIME_CATALOG: Record<AgentRuntimeId, RuntimeCatalogEntry> = {
|
|
|
114
201
|
profileAssist: false,
|
|
115
202
|
authHealthCheck: true,
|
|
116
203
|
},
|
|
204
|
+
features: {
|
|
205
|
+
// Direct Responses API — no SDK-native skill machinery.
|
|
206
|
+
// Revisit when chat-claude-sdk-skills designs direct-API skill injection.
|
|
207
|
+
hasNativeSkills: false,
|
|
208
|
+
hasProgressiveDisclosure: false,
|
|
209
|
+
hasFilesystemTools: false,
|
|
210
|
+
hasBash: false,
|
|
211
|
+
hasTodoWrite: false,
|
|
212
|
+
hasSubagentDelegation: false,
|
|
213
|
+
hasHooks: false,
|
|
214
|
+
autoLoadsInstructions: null,
|
|
215
|
+
stagentInjectsSkills: false,
|
|
216
|
+
supportsSkillComposition: true,
|
|
217
|
+
maxActiveSkills: 3,
|
|
218
|
+
},
|
|
117
219
|
models: {
|
|
118
220
|
default: "gpt-4.1",
|
|
119
221
|
supported: ["gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano"],
|
|
@@ -134,6 +236,19 @@ const RUNTIME_CATALOG: Record<AgentRuntimeId, RuntimeCatalogEntry> = {
|
|
|
134
236
|
profileAssist: false,
|
|
135
237
|
authHealthCheck: true,
|
|
136
238
|
},
|
|
239
|
+
features: {
|
|
240
|
+
hasNativeSkills: false,
|
|
241
|
+
hasProgressiveDisclosure: false,
|
|
242
|
+
hasFilesystemTools: false,
|
|
243
|
+
hasBash: false,
|
|
244
|
+
hasTodoWrite: false, // Stagent MCP exposes todo tools separately
|
|
245
|
+
hasSubagentDelegation: false,
|
|
246
|
+
hasHooks: false,
|
|
247
|
+
autoLoadsInstructions: null,
|
|
248
|
+
stagentInjectsSkills: true,
|
|
249
|
+
supportsSkillComposition: false,
|
|
250
|
+
maxActiveSkills: 1,
|
|
251
|
+
},
|
|
137
252
|
models: {
|
|
138
253
|
default: "llama3",
|
|
139
254
|
supported: [], // Dynamic — populated from Ollama API at runtime
|
|
@@ -157,6 +272,12 @@ export function getRuntimeCapabilities(
|
|
|
157
272
|
return getRuntimeCatalogEntry(runtimeId).capabilities;
|
|
158
273
|
}
|
|
159
274
|
|
|
275
|
+
export function getRuntimeFeatures(
|
|
276
|
+
runtimeId: AgentRuntimeId = DEFAULT_AGENT_RUNTIME
|
|
277
|
+
): RuntimeFeatures {
|
|
278
|
+
return getRuntimeCatalogEntry(runtimeId).features;
|
|
279
|
+
}
|
|
280
|
+
|
|
160
281
|
export function resolveAgentRuntime(runtimeId?: string | null): AgentRuntimeId {
|
|
161
282
|
if (!runtimeId) return DEFAULT_AGENT_RUNTIME;
|
|
162
283
|
if (isAgentRuntimeId(runtimeId)) return runtimeId;
|
|
@@ -1,3 +1,35 @@
|
|
|
1
|
+
// ─── Claude Agent SDK options shared by chat and task runtimes ──────
|
|
2
|
+
//
|
|
3
|
+
// Chat (src/lib/chat/engine.ts) and task (src/lib/agents/claude-agent.ts)
|
|
4
|
+
// both construct query() options for the `claude-code` runtime. These
|
|
5
|
+
// constants are the single source of truth so the two code paths cannot
|
|
6
|
+
// drift — a drift that would manifest as "skills work in chat but vanish
|
|
7
|
+
// in tasks on the same project." See features/task-runtime-skill-parity.md
|
|
8
|
+
// and features/chat-claude-sdk-skills.md.
|
|
9
|
+
|
|
10
|
+
export const CLAUDE_SDK_SETTING_SOURCES = ["user", "project"] as const;
|
|
11
|
+
|
|
12
|
+
export const CLAUDE_SDK_ALLOWED_TOOLS = [
|
|
13
|
+
"Skill",
|
|
14
|
+
"Read",
|
|
15
|
+
"Grep",
|
|
16
|
+
"Glob",
|
|
17
|
+
"Edit",
|
|
18
|
+
"Write",
|
|
19
|
+
"Bash",
|
|
20
|
+
"TodoWrite",
|
|
21
|
+
] as const;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Filesystem tools safe to auto-allow without a permission prompt.
|
|
25
|
+
* Mirrors the existing browser/exa read-only auto-allow pattern.
|
|
26
|
+
*/
|
|
27
|
+
export const CLAUDE_SDK_READ_ONLY_FS_TOOLS = new Set<string>([
|
|
28
|
+
"Read",
|
|
29
|
+
"Grep",
|
|
30
|
+
"Glob",
|
|
31
|
+
]);
|
|
32
|
+
|
|
1
33
|
/**
|
|
2
34
|
* Build the environment for the Claude Agent SDK subprocess.
|
|
3
35
|
*
|
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
import { getProfile } from "@/lib/agents/profiles/registry";
|
|
2
|
+
import { profileSupportsRuntime } from "@/lib/agents/profiles/compatibility";
|
|
3
|
+
import { suggestRuntime } from "@/lib/agents/router";
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_AGENT_RUNTIME,
|
|
6
|
+
getRuntimeCatalogEntry,
|
|
7
|
+
getRuntimeFeatures,
|
|
8
|
+
resolveAgentRuntime,
|
|
9
|
+
type AgentRuntimeId,
|
|
10
|
+
} from "./catalog";
|
|
11
|
+
import { testRuntimeConnection } from "./index";
|
|
12
|
+
import { getRoutingPreference } from "@/lib/settings/routing";
|
|
13
|
+
import { getRuntimeSetupStates, listConfiguredRuntimeIds } from "@/lib/settings/runtime-setup";
|
|
14
|
+
import { DEFAULT_CHAT_MODEL, getRuntimeForModel } from "@/lib/chat/types";
|
|
15
|
+
|
|
16
|
+
const FILESYSTEM_TOOL_NAMES = new Set([
|
|
17
|
+
"Read",
|
|
18
|
+
"Write",
|
|
19
|
+
"Edit",
|
|
20
|
+
"MultiEdit",
|
|
21
|
+
"Grep",
|
|
22
|
+
"Glob",
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
const CHAT_MODEL_FALLBACKS: Record<string, string[]> = {
|
|
26
|
+
haiku: ["gpt-5.4-mini", "gpt-5.3-codex", "gpt-5.4"],
|
|
27
|
+
sonnet: ["gpt-5.3-codex", "gpt-5.4", "gpt-5.4-mini"],
|
|
28
|
+
opus: ["gpt-5.4", "gpt-5.3-codex", "gpt-5.4-mini"],
|
|
29
|
+
"gpt-5.4-mini": ["haiku", "sonnet", "opus"],
|
|
30
|
+
"gpt-5.3-codex": ["sonnet", "haiku", "opus"],
|
|
31
|
+
"gpt-5.4": ["opus", "sonnet", "haiku"],
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export class RuntimeUnavailableError extends Error {
|
|
35
|
+
constructor(message: string) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = "RuntimeUnavailableError";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class RequestedModelUnavailableError extends Error {
|
|
42
|
+
constructor(message: string) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.name = "RequestedModelUnavailableError";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class NoCompatibleRuntimeError extends Error {
|
|
49
|
+
constructor(message: string) {
|
|
50
|
+
super(message);
|
|
51
|
+
this.name = "NoCompatibleRuntimeError";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface ResolvedExecutionTarget {
|
|
56
|
+
requestedRuntimeId: AgentRuntimeId | null;
|
|
57
|
+
effectiveRuntimeId: AgentRuntimeId;
|
|
58
|
+
requestedModelId: string | null;
|
|
59
|
+
effectiveModelId: string | null;
|
|
60
|
+
fallbackApplied: boolean;
|
|
61
|
+
fallbackReason: string | null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
type RuntimeRequirements = {
|
|
65
|
+
requiresBash: boolean;
|
|
66
|
+
requiresFilesystem: boolean;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
type RuntimeAvailability = {
|
|
70
|
+
available: boolean;
|
|
71
|
+
reason: string | null;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
function unique<T>(values: T[]): T[] {
|
|
75
|
+
return Array.from(new Set(values));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function getRuntimeLabel(runtimeId: AgentRuntimeId): string {
|
|
79
|
+
return getRuntimeCatalogEntry(runtimeId).label;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function detectRuntimeRequirements(profileId?: string | null): RuntimeRequirements {
|
|
83
|
+
const profile = profileId ? getProfile(profileId) : undefined;
|
|
84
|
+
const allowedTools = profile?.allowedTools ?? [];
|
|
85
|
+
|
|
86
|
+
const requiresBash = allowedTools.some(
|
|
87
|
+
(tool) => tool === "Bash" || tool.startsWith("Bash(")
|
|
88
|
+
);
|
|
89
|
+
const requiresFilesystem =
|
|
90
|
+
requiresBash ||
|
|
91
|
+
allowedTools.some((tool) => FILESYSTEM_TOOL_NAMES.has(tool));
|
|
92
|
+
|
|
93
|
+
return { requiresBash, requiresFilesystem };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function runtimeMeetsRequirements(
|
|
97
|
+
runtimeId: AgentRuntimeId,
|
|
98
|
+
requirements: RuntimeRequirements
|
|
99
|
+
): boolean {
|
|
100
|
+
const features = getRuntimeFeatures(runtimeId);
|
|
101
|
+
if (requirements.requiresBash && !features.hasBash) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
if (requirements.requiresFilesystem && !features.hasFilesystemTools) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function filterCompatibleRuntimes(
|
|
111
|
+
runtimeIds: AgentRuntimeId[],
|
|
112
|
+
profileId?: string | null
|
|
113
|
+
): AgentRuntimeId[] {
|
|
114
|
+
if (!profileId) {
|
|
115
|
+
return runtimeIds;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const profile = getProfile(profileId);
|
|
119
|
+
if (!profile) {
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return runtimeIds.filter((runtimeId) =>
|
|
124
|
+
profileSupportsRuntime(profile, runtimeId)
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function checkRuntimeAvailability(
|
|
129
|
+
runtimeId: AgentRuntimeId
|
|
130
|
+
): Promise<RuntimeAvailability> {
|
|
131
|
+
const states = await getRuntimeSetupStates();
|
|
132
|
+
if (!states[runtimeId]?.configured) {
|
|
133
|
+
return {
|
|
134
|
+
available: false,
|
|
135
|
+
reason: `${getRuntimeLabel(runtimeId)} is not configured`,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
const connection = await testRuntimeConnection(runtimeId);
|
|
141
|
+
if (connection.connected) {
|
|
142
|
+
return { available: true, reason: null };
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
available: false,
|
|
146
|
+
reason:
|
|
147
|
+
connection.error ??
|
|
148
|
+
`${getRuntimeLabel(runtimeId)} is unavailable`,
|
|
149
|
+
};
|
|
150
|
+
} catch (error) {
|
|
151
|
+
return {
|
|
152
|
+
available: false,
|
|
153
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function getConfiguredCandidateRuntimes(
|
|
159
|
+
profileId?: string | null
|
|
160
|
+
): Promise<AgentRuntimeId[]> {
|
|
161
|
+
const states = await getRuntimeSetupStates();
|
|
162
|
+
return filterCompatibleRuntimes(
|
|
163
|
+
listConfiguredRuntimeIds(states) as AgentRuntimeId[],
|
|
164
|
+
profileId
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function buildTaskFallbackOrder(input: {
|
|
169
|
+
title: string;
|
|
170
|
+
description?: string | null;
|
|
171
|
+
profileId?: string | null;
|
|
172
|
+
requestedRuntimeId: AgentRuntimeId | null;
|
|
173
|
+
compatibleRuntimeIds: AgentRuntimeId[];
|
|
174
|
+
}): AgentRuntimeId[] {
|
|
175
|
+
const alternates = input.compatibleRuntimeIds.filter(
|
|
176
|
+
(runtimeId) => runtimeId !== input.requestedRuntimeId
|
|
177
|
+
);
|
|
178
|
+
if (alternates.length === 0) {
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const preferred = suggestRuntime(
|
|
183
|
+
input.title,
|
|
184
|
+
input.description,
|
|
185
|
+
input.profileId,
|
|
186
|
+
alternates,
|
|
187
|
+
"quality"
|
|
188
|
+
).runtimeId;
|
|
189
|
+
|
|
190
|
+
return unique([
|
|
191
|
+
preferred,
|
|
192
|
+
...alternates.filter(
|
|
193
|
+
(runtimeId) =>
|
|
194
|
+
input.requestedRuntimeId != null &&
|
|
195
|
+
getRuntimeCatalogEntry(runtimeId).providerId ===
|
|
196
|
+
getRuntimeCatalogEntry(input.requestedRuntimeId).providerId
|
|
197
|
+
),
|
|
198
|
+
...alternates,
|
|
199
|
+
]);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function buildRuntimeFallbackReason(input: {
|
|
203
|
+
requestedRuntimeId: AgentRuntimeId | null;
|
|
204
|
+
effectiveRuntimeId: AgentRuntimeId;
|
|
205
|
+
unavailableReason: string | null;
|
|
206
|
+
}): string | null {
|
|
207
|
+
if (!input.requestedRuntimeId) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const requestedLabel = getRuntimeLabel(input.requestedRuntimeId);
|
|
212
|
+
const effectiveLabel = getRuntimeLabel(input.effectiveRuntimeId);
|
|
213
|
+
const reason = input.unavailableReason ?? `${requestedLabel} is unavailable`;
|
|
214
|
+
return `${reason}. Fell back to ${effectiveLabel}.`;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export async function resolveTaskExecutionTarget(input: {
|
|
218
|
+
title: string;
|
|
219
|
+
description?: string | null;
|
|
220
|
+
requestedRuntimeId?: string | null;
|
|
221
|
+
profileId?: string | null;
|
|
222
|
+
unavailableRuntimeIds?: string[];
|
|
223
|
+
unavailableReasons?: Record<string, string>;
|
|
224
|
+
}): Promise<ResolvedExecutionTarget> {
|
|
225
|
+
const requestedRuntimeId = input.requestedRuntimeId
|
|
226
|
+
? resolveAgentRuntime(input.requestedRuntimeId)
|
|
227
|
+
: null;
|
|
228
|
+
const requirements = detectRuntimeRequirements(input.profileId);
|
|
229
|
+
const unavailableRuntimeIds = new Set(
|
|
230
|
+
(input.unavailableRuntimeIds ?? []).map((runtimeId) =>
|
|
231
|
+
resolveAgentRuntime(runtimeId)
|
|
232
|
+
)
|
|
233
|
+
);
|
|
234
|
+
const configuredCandidates = await getConfiguredCandidateRuntimes(input.profileId);
|
|
235
|
+
const compatibleCandidates = configuredCandidates.filter((runtimeId) =>
|
|
236
|
+
runtimeMeetsRequirements(runtimeId, requirements)
|
|
237
|
+
);
|
|
238
|
+
const launchableCandidates = compatibleCandidates.filter(
|
|
239
|
+
(runtimeId) => !unavailableRuntimeIds.has(runtimeId)
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
if (compatibleCandidates.length === 0) {
|
|
243
|
+
throw new NoCompatibleRuntimeError(
|
|
244
|
+
"No compatible configured runtime is available for this task."
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (requestedRuntimeId) {
|
|
249
|
+
if (
|
|
250
|
+
compatibleCandidates.includes(requestedRuntimeId) &&
|
|
251
|
+
!unavailableRuntimeIds.has(requestedRuntimeId) &&
|
|
252
|
+
(await checkRuntimeAvailability(requestedRuntimeId)).available
|
|
253
|
+
) {
|
|
254
|
+
return {
|
|
255
|
+
requestedRuntimeId,
|
|
256
|
+
effectiveRuntimeId: requestedRuntimeId,
|
|
257
|
+
requestedModelId: null,
|
|
258
|
+
effectiveModelId: null,
|
|
259
|
+
fallbackApplied: false,
|
|
260
|
+
fallbackReason: null,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const availability = unavailableRuntimeIds.has(requestedRuntimeId)
|
|
265
|
+
? {
|
|
266
|
+
available: false,
|
|
267
|
+
reason:
|
|
268
|
+
input.unavailableReasons?.[requestedRuntimeId] ??
|
|
269
|
+
`${getRuntimeLabel(requestedRuntimeId)} is temporarily unavailable`,
|
|
270
|
+
}
|
|
271
|
+
: compatibleCandidates.includes(requestedRuntimeId)
|
|
272
|
+
? await checkRuntimeAvailability(requestedRuntimeId)
|
|
273
|
+
: {
|
|
274
|
+
available: false,
|
|
275
|
+
reason: `${getRuntimeLabel(requestedRuntimeId)} does not support this task/profile`,
|
|
276
|
+
};
|
|
277
|
+
const fallbackOrder = buildTaskFallbackOrder({
|
|
278
|
+
title: input.title,
|
|
279
|
+
description: input.description,
|
|
280
|
+
profileId: input.profileId,
|
|
281
|
+
requestedRuntimeId,
|
|
282
|
+
compatibleRuntimeIds: launchableCandidates,
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
for (const candidate of fallbackOrder) {
|
|
286
|
+
const candidateAvailability = await checkRuntimeAvailability(candidate);
|
|
287
|
+
if (candidateAvailability.available) {
|
|
288
|
+
return {
|
|
289
|
+
requestedRuntimeId,
|
|
290
|
+
effectiveRuntimeId: candidate,
|
|
291
|
+
requestedModelId: null,
|
|
292
|
+
effectiveModelId: null,
|
|
293
|
+
fallbackApplied: true,
|
|
294
|
+
fallbackReason: buildRuntimeFallbackReason({
|
|
295
|
+
requestedRuntimeId,
|
|
296
|
+
effectiveRuntimeId: candidate,
|
|
297
|
+
unavailableReason: availability.reason,
|
|
298
|
+
}),
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
throw new NoCompatibleRuntimeError(
|
|
304
|
+
availability.reason ??
|
|
305
|
+
`No healthy alternate runtime is available for ${getRuntimeLabel(requestedRuntimeId)}.`
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const routingPreference = await getRoutingPreference();
|
|
310
|
+
const suggested = suggestRuntime(
|
|
311
|
+
input.title,
|
|
312
|
+
input.description,
|
|
313
|
+
input.profileId,
|
|
314
|
+
launchableCandidates,
|
|
315
|
+
routingPreference
|
|
316
|
+
).runtimeId;
|
|
317
|
+
const autoOrder = unique([
|
|
318
|
+
suggested,
|
|
319
|
+
...launchableCandidates,
|
|
320
|
+
]);
|
|
321
|
+
|
|
322
|
+
for (const candidate of autoOrder) {
|
|
323
|
+
const availability = await checkRuntimeAvailability(candidate);
|
|
324
|
+
if (availability.available) {
|
|
325
|
+
return {
|
|
326
|
+
requestedRuntimeId: null,
|
|
327
|
+
effectiveRuntimeId: candidate,
|
|
328
|
+
requestedModelId: null,
|
|
329
|
+
effectiveModelId: null,
|
|
330
|
+
fallbackApplied: false,
|
|
331
|
+
fallbackReason: null,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
throw new RuntimeUnavailableError(
|
|
337
|
+
"No healthy runtime is currently available to execute this task."
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export async function resolveResumeExecutionTarget(input: {
|
|
342
|
+
requestedRuntimeId?: string | null;
|
|
343
|
+
effectiveRuntimeId?: string | null;
|
|
344
|
+
}): Promise<ResolvedExecutionTarget> {
|
|
345
|
+
const requestedRuntimeId = input.requestedRuntimeId
|
|
346
|
+
? resolveAgentRuntime(input.requestedRuntimeId)
|
|
347
|
+
: null;
|
|
348
|
+
const resumeRuntimeId = input.effectiveRuntimeId
|
|
349
|
+
? resolveAgentRuntime(input.effectiveRuntimeId)
|
|
350
|
+
: requestedRuntimeId ?? DEFAULT_AGENT_RUNTIME;
|
|
351
|
+
const availability = await checkRuntimeAvailability(resumeRuntimeId);
|
|
352
|
+
|
|
353
|
+
if (!availability.available) {
|
|
354
|
+
throw new RuntimeUnavailableError(
|
|
355
|
+
availability.reason ??
|
|
356
|
+
`${getRuntimeLabel(resumeRuntimeId)} is unavailable for resume. Use Retry for a fresh execution.`
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return {
|
|
361
|
+
requestedRuntimeId,
|
|
362
|
+
effectiveRuntimeId: resumeRuntimeId,
|
|
363
|
+
requestedModelId: null,
|
|
364
|
+
effectiveModelId: null,
|
|
365
|
+
fallbackApplied: false,
|
|
366
|
+
fallbackReason: null,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function buildChatFallbackOrder(requestedModelId: string): string[] {
|
|
371
|
+
const fallbacks = CHAT_MODEL_FALLBACKS[requestedModelId] ?? [];
|
|
372
|
+
return unique([requestedModelId, ...fallbacks]);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function buildChatFallbackReason(input: {
|
|
376
|
+
requestedRuntimeId: AgentRuntimeId;
|
|
377
|
+
effectiveRuntimeId: AgentRuntimeId;
|
|
378
|
+
requestedModelId: string;
|
|
379
|
+
effectiveModelId: string;
|
|
380
|
+
unavailableReason: string | null;
|
|
381
|
+
}): string | null {
|
|
382
|
+
if (
|
|
383
|
+
input.requestedRuntimeId === input.effectiveRuntimeId &&
|
|
384
|
+
input.requestedModelId === input.effectiveModelId
|
|
385
|
+
) {
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const requestedLabel = `${input.requestedModelId} on ${getRuntimeLabel(input.requestedRuntimeId)}`;
|
|
390
|
+
const effectiveLabel = `${input.effectiveModelId} on ${getRuntimeLabel(input.effectiveRuntimeId)}`;
|
|
391
|
+
const reason = input.unavailableReason ?? `${requestedLabel} is unavailable`;
|
|
392
|
+
return `${reason}. Using ${effectiveLabel} for this turn.`;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export async function resolveChatExecutionTarget(input: {
|
|
396
|
+
requestedRuntimeId?: string | null;
|
|
397
|
+
requestedModelId?: string | null;
|
|
398
|
+
}): Promise<ResolvedExecutionTarget> {
|
|
399
|
+
const requestedModelId =
|
|
400
|
+
input.requestedModelId ??
|
|
401
|
+
(input.requestedRuntimeId
|
|
402
|
+
? getRuntimeCatalogEntry(resolveAgentRuntime(input.requestedRuntimeId)).models.default
|
|
403
|
+
: DEFAULT_CHAT_MODEL);
|
|
404
|
+
const requestedRuntimeId = resolveAgentRuntime(
|
|
405
|
+
input.requestedRuntimeId ?? getRuntimeForModel(requestedModelId)
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
const modelOrder = buildChatFallbackOrder(requestedModelId);
|
|
409
|
+
let requestedAvailability: RuntimeAvailability | null = null;
|
|
410
|
+
|
|
411
|
+
for (const candidateModelId of modelOrder) {
|
|
412
|
+
const candidateRuntimeId = resolveAgentRuntime(
|
|
413
|
+
getRuntimeForModel(candidateModelId)
|
|
414
|
+
);
|
|
415
|
+
if (
|
|
416
|
+
candidateRuntimeId !== "claude-code" &&
|
|
417
|
+
candidateRuntimeId !== "openai-codex-app-server" &&
|
|
418
|
+
candidateRuntimeId !== "ollama"
|
|
419
|
+
) {
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const availability = await checkRuntimeAvailability(candidateRuntimeId);
|
|
424
|
+
if (
|
|
425
|
+
candidateRuntimeId === requestedRuntimeId &&
|
|
426
|
+
requestedAvailability === null
|
|
427
|
+
) {
|
|
428
|
+
requestedAvailability = availability;
|
|
429
|
+
}
|
|
430
|
+
if (!availability.available) {
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return {
|
|
435
|
+
requestedRuntimeId,
|
|
436
|
+
effectiveRuntimeId: candidateRuntimeId,
|
|
437
|
+
requestedModelId,
|
|
438
|
+
effectiveModelId: candidateModelId,
|
|
439
|
+
fallbackApplied:
|
|
440
|
+
candidateRuntimeId !== requestedRuntimeId ||
|
|
441
|
+
candidateModelId !== requestedModelId,
|
|
442
|
+
fallbackReason: buildChatFallbackReason({
|
|
443
|
+
requestedRuntimeId,
|
|
444
|
+
effectiveRuntimeId: candidateRuntimeId,
|
|
445
|
+
requestedModelId,
|
|
446
|
+
effectiveModelId: candidateModelId,
|
|
447
|
+
unavailableReason: requestedAvailability?.reason ?? null,
|
|
448
|
+
}),
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
throw new RequestedModelUnavailableError(
|
|
453
|
+
requestedAvailability?.reason ??
|
|
454
|
+
`No healthy runtime is available for ${requestedModelId}.`
|
|
455
|
+
);
|
|
456
|
+
}
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
DEFAULT_AGENT_RUNTIME,
|
|
3
3
|
getRuntimeCapabilities,
|
|
4
4
|
getRuntimeCatalogEntry,
|
|
5
|
+
getRuntimeFeatures,
|
|
5
6
|
listRuntimeCatalog,
|
|
6
7
|
resolveAgentRuntime,
|
|
7
8
|
type AgentRuntimeId,
|
|
@@ -187,3 +188,6 @@ export async function testRuntimeConnection(
|
|
|
187
188
|
}
|
|
188
189
|
return adapter.testConnection();
|
|
189
190
|
}
|
|
191
|
+
|
|
192
|
+
export { getRuntimeFeatures };
|
|
193
|
+
export type { RuntimeFeatures } from "./catalog";
|