stagent 0.10.0 → 0.11.0
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 +15 -2
- 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/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-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 +1 -1
- 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/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/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 +18 -0
- 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 +54 -0
- 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__/upgrade-poller.test.ts +50 -0
- package/src/lib/instance/fingerprint.ts +7 -9
- package/src/lib/instance/upgrade-poller.ts +53 -1
- package/src/lib/schedules/scheduler.ts +4 -4
- 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
package/src/lib/agents/router.ts
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import { listProfiles, getProfile } from "./profiles/registry";
|
|
2
2
|
import { profileSupportsRuntime } from "./profiles/compatibility";
|
|
3
|
-
import {
|
|
4
|
-
executeTaskWithRuntime,
|
|
5
|
-
resumeTaskWithRuntime,
|
|
6
|
-
} from "./runtime";
|
|
7
3
|
import {
|
|
8
4
|
DEFAULT_AGENT_RUNTIME,
|
|
9
5
|
SUPPORTED_AGENT_RUNTIMES,
|
|
10
6
|
type AgentRuntimeId,
|
|
11
7
|
} from "./runtime/catalog";
|
|
12
8
|
import type { RoutingPreference } from "@/lib/constants/settings";
|
|
9
|
+
import { resumeTaskExecution, startTaskExecution } from "./task-dispatch";
|
|
13
10
|
|
|
14
11
|
// ── Keyword signal maps for runtime scoring ──────────────────────────
|
|
15
12
|
|
|
@@ -217,12 +214,12 @@ export async function executeTaskWithAgent(
|
|
|
217
214
|
taskId: string,
|
|
218
215
|
agentType: string | null | undefined = DEFAULT_AGENT_RUNTIME
|
|
219
216
|
): Promise<void> {
|
|
220
|
-
return
|
|
217
|
+
return startTaskExecution(taskId, { requestedRuntimeId: agentType });
|
|
221
218
|
}
|
|
222
219
|
|
|
223
220
|
export async function resumeTaskWithAgent(
|
|
224
221
|
taskId: string,
|
|
225
222
|
agentType: string | null | undefined = DEFAULT_AGENT_RUNTIME
|
|
226
223
|
): Promise<void> {
|
|
227
|
-
return
|
|
224
|
+
return resumeTaskExecution(taskId, { requestedRuntimeId: agentType });
|
|
228
225
|
}
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
DEFAULT_AGENT_RUNTIME,
|
|
4
4
|
getRuntimeCapabilities,
|
|
5
5
|
getRuntimeCatalogEntry,
|
|
6
|
+
getRuntimeFeatures,
|
|
6
7
|
listRuntimeCatalog,
|
|
7
8
|
resolveAgentRuntime,
|
|
8
9
|
} from "@/lib/agents/runtime/catalog";
|
|
@@ -46,4 +47,133 @@ describe("runtime catalog", () => {
|
|
|
46
47
|
expect(result).toBe("claude-code");
|
|
47
48
|
warnSpy.mockRestore();
|
|
48
49
|
});
|
|
50
|
+
|
|
51
|
+
it("exposes LLM-surface features via getRuntimeFeatures", () => {
|
|
52
|
+
const features = getRuntimeFeatures("claude-code");
|
|
53
|
+
expect(features.hasNativeSkills).toBe(true);
|
|
54
|
+
expect(features.hasProgressiveDisclosure).toBe(true);
|
|
55
|
+
expect(features.autoLoadsInstructions).toBe("CLAUDE.md");
|
|
56
|
+
expect(features.stagentInjectsSkills).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("marks Ollama as requiring Stagent-injected skills", () => {
|
|
60
|
+
const features = getRuntimeFeatures("ollama");
|
|
61
|
+
expect(features.hasNativeSkills).toBe(false);
|
|
62
|
+
expect(features.stagentInjectsSkills).toBe(true);
|
|
63
|
+
expect(features.autoLoadsInstructions).toBeNull();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("declares Codex auto-loads AGENTS.md", () => {
|
|
67
|
+
expect(getRuntimeFeatures("openai-codex-app-server").autoLoadsInstructions).toBe("AGENTS.md");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("every runtime declares every feature key (exhaustiveness guard)", () => {
|
|
71
|
+
const runtimes = listRuntimeCatalog();
|
|
72
|
+
const expectedKeys: Array<keyof ReturnType<typeof getRuntimeFeatures>> = [
|
|
73
|
+
"hasNativeSkills",
|
|
74
|
+
"hasProgressiveDisclosure",
|
|
75
|
+
"hasFilesystemTools",
|
|
76
|
+
"hasBash",
|
|
77
|
+
"hasTodoWrite",
|
|
78
|
+
"hasSubagentDelegation",
|
|
79
|
+
"hasHooks",
|
|
80
|
+
"autoLoadsInstructions",
|
|
81
|
+
"stagentInjectsSkills",
|
|
82
|
+
"supportsSkillComposition",
|
|
83
|
+
"maxActiveSkills",
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
// Guard against the "list grows stale" failure mode: if a new key is added
|
|
87
|
+
// to RuntimeFeatures but not to expectedKeys above, this catches it.
|
|
88
|
+
expect(expectedKeys.length).toBe(Object.keys(getRuntimeFeatures()).length);
|
|
89
|
+
|
|
90
|
+
for (const runtime of runtimes) {
|
|
91
|
+
for (const key of expectedKeys) {
|
|
92
|
+
expect(
|
|
93
|
+
runtime.features,
|
|
94
|
+
`${runtime.id} missing feature "${key}"`
|
|
95
|
+
).toHaveProperty(key);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("feature matrix snapshot matches declared values", () => {
|
|
101
|
+
// Guard against silent regressions: the declared feature matrix must match
|
|
102
|
+
// this snapshot exactly. Update intentionally when flipping a capability flag
|
|
103
|
+
// (and reference the spec change in the commit message).
|
|
104
|
+
const snapshot = listRuntimeCatalog().reduce<Record<string, unknown>>((acc, r) => {
|
|
105
|
+
acc[r.id] = r.features;
|
|
106
|
+
return acc;
|
|
107
|
+
}, {});
|
|
108
|
+
|
|
109
|
+
expect(snapshot).toMatchInlineSnapshot(`
|
|
110
|
+
{
|
|
111
|
+
"anthropic-direct": {
|
|
112
|
+
"autoLoadsInstructions": null,
|
|
113
|
+
"hasBash": false,
|
|
114
|
+
"hasFilesystemTools": false,
|
|
115
|
+
"hasHooks": false,
|
|
116
|
+
"hasNativeSkills": false,
|
|
117
|
+
"hasProgressiveDisclosure": false,
|
|
118
|
+
"hasSubagentDelegation": false,
|
|
119
|
+
"hasTodoWrite": false,
|
|
120
|
+
"maxActiveSkills": 3,
|
|
121
|
+
"stagentInjectsSkills": false,
|
|
122
|
+
"supportsSkillComposition": true,
|
|
123
|
+
},
|
|
124
|
+
"claude-code": {
|
|
125
|
+
"autoLoadsInstructions": "CLAUDE.md",
|
|
126
|
+
"hasBash": true,
|
|
127
|
+
"hasFilesystemTools": true,
|
|
128
|
+
"hasHooks": false,
|
|
129
|
+
"hasNativeSkills": true,
|
|
130
|
+
"hasProgressiveDisclosure": true,
|
|
131
|
+
"hasSubagentDelegation": false,
|
|
132
|
+
"hasTodoWrite": true,
|
|
133
|
+
"maxActiveSkills": 3,
|
|
134
|
+
"stagentInjectsSkills": false,
|
|
135
|
+
"supportsSkillComposition": true,
|
|
136
|
+
},
|
|
137
|
+
"ollama": {
|
|
138
|
+
"autoLoadsInstructions": null,
|
|
139
|
+
"hasBash": false,
|
|
140
|
+
"hasFilesystemTools": false,
|
|
141
|
+
"hasHooks": false,
|
|
142
|
+
"hasNativeSkills": false,
|
|
143
|
+
"hasProgressiveDisclosure": false,
|
|
144
|
+
"hasSubagentDelegation": false,
|
|
145
|
+
"hasTodoWrite": false,
|
|
146
|
+
"maxActiveSkills": 1,
|
|
147
|
+
"stagentInjectsSkills": true,
|
|
148
|
+
"supportsSkillComposition": false,
|
|
149
|
+
},
|
|
150
|
+
"openai-codex-app-server": {
|
|
151
|
+
"autoLoadsInstructions": "AGENTS.md",
|
|
152
|
+
"hasBash": true,
|
|
153
|
+
"hasFilesystemTools": true,
|
|
154
|
+
"hasHooks": false,
|
|
155
|
+
"hasNativeSkills": true,
|
|
156
|
+
"hasProgressiveDisclosure": true,
|
|
157
|
+
"hasSubagentDelegation": false,
|
|
158
|
+
"hasTodoWrite": true,
|
|
159
|
+
"maxActiveSkills": 3,
|
|
160
|
+
"stagentInjectsSkills": false,
|
|
161
|
+
"supportsSkillComposition": true,
|
|
162
|
+
},
|
|
163
|
+
"openai-direct": {
|
|
164
|
+
"autoLoadsInstructions": null,
|
|
165
|
+
"hasBash": false,
|
|
166
|
+
"hasFilesystemTools": false,
|
|
167
|
+
"hasHooks": false,
|
|
168
|
+
"hasNativeSkills": false,
|
|
169
|
+
"hasProgressiveDisclosure": false,
|
|
170
|
+
"hasSubagentDelegation": false,
|
|
171
|
+
"hasTodoWrite": false,
|
|
172
|
+
"maxActiveSkills": 3,
|
|
173
|
+
"stagentInjectsSkills": false,
|
|
174
|
+
"supportsSkillComposition": true,
|
|
175
|
+
},
|
|
176
|
+
}
|
|
177
|
+
`);
|
|
178
|
+
});
|
|
49
179
|
});
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import type { AgentRuntimeId } from "@/lib/agents/runtime/catalog";
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
mockGetRuntimeSetupStates,
|
|
6
|
+
mockListConfiguredRuntimeIds,
|
|
7
|
+
mockGetRoutingPreference,
|
|
8
|
+
mockTestRuntimeConnection,
|
|
9
|
+
mockGetProfile,
|
|
10
|
+
mockProfileSupportsRuntime,
|
|
11
|
+
mockSuggestRuntime,
|
|
12
|
+
} = vi.hoisted(() => ({
|
|
13
|
+
mockGetRuntimeSetupStates: vi.fn(),
|
|
14
|
+
mockListConfiguredRuntimeIds: vi.fn(),
|
|
15
|
+
mockGetRoutingPreference: vi.fn(),
|
|
16
|
+
mockTestRuntimeConnection: vi.fn(),
|
|
17
|
+
mockGetProfile: vi.fn(),
|
|
18
|
+
mockProfileSupportsRuntime: vi.fn(),
|
|
19
|
+
mockSuggestRuntime: vi.fn(),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
vi.mock("@/lib/settings/runtime-setup", () => ({
|
|
23
|
+
getRuntimeSetupStates: mockGetRuntimeSetupStates,
|
|
24
|
+
listConfiguredRuntimeIds: mockListConfiguredRuntimeIds,
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
vi.mock("@/lib/settings/routing", () => ({
|
|
28
|
+
getRoutingPreference: mockGetRoutingPreference,
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
vi.mock("@/lib/agents/runtime/index", () => ({
|
|
32
|
+
testRuntimeConnection: mockTestRuntimeConnection,
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
vi.mock("@/lib/agents/profiles/registry", () => ({
|
|
36
|
+
getProfile: mockGetProfile,
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
vi.mock("@/lib/agents/profiles/compatibility", () => ({
|
|
40
|
+
profileSupportsRuntime: mockProfileSupportsRuntime,
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
vi.mock("@/lib/agents/router", () => ({
|
|
44
|
+
suggestRuntime: mockSuggestRuntime,
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
import {
|
|
48
|
+
RequestedModelUnavailableError,
|
|
49
|
+
resolveChatExecutionTarget,
|
|
50
|
+
resolveResumeExecutionTarget,
|
|
51
|
+
resolveTaskExecutionTarget,
|
|
52
|
+
} from "../execution-target";
|
|
53
|
+
|
|
54
|
+
function makeStates(configured: AgentRuntimeId[]) {
|
|
55
|
+
const all: AgentRuntimeId[] = [
|
|
56
|
+
"claude-code",
|
|
57
|
+
"openai-codex-app-server",
|
|
58
|
+
"anthropic-direct",
|
|
59
|
+
"openai-direct",
|
|
60
|
+
"ollama",
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
return Object.fromEntries(
|
|
64
|
+
all.map((runtimeId) => [
|
|
65
|
+
runtimeId,
|
|
66
|
+
{
|
|
67
|
+
runtimeId,
|
|
68
|
+
configured: configured.includes(runtimeId),
|
|
69
|
+
},
|
|
70
|
+
])
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
describe("execution target resolver", () => {
|
|
75
|
+
beforeEach(() => {
|
|
76
|
+
vi.clearAllMocks();
|
|
77
|
+
mockGetRuntimeSetupStates.mockResolvedValue(
|
|
78
|
+
makeStates(["claude-code", "openai-codex-app-server"])
|
|
79
|
+
);
|
|
80
|
+
mockListConfiguredRuntimeIds.mockReturnValue([
|
|
81
|
+
"claude-code",
|
|
82
|
+
"openai-codex-app-server",
|
|
83
|
+
]);
|
|
84
|
+
mockGetRoutingPreference.mockResolvedValue("latency");
|
|
85
|
+
mockProfileSupportsRuntime.mockReturnValue(true);
|
|
86
|
+
mockSuggestRuntime.mockImplementation(
|
|
87
|
+
(
|
|
88
|
+
_title: string,
|
|
89
|
+
_description: string | undefined,
|
|
90
|
+
_profileId: string | undefined,
|
|
91
|
+
availableRuntimeIds: AgentRuntimeId[]
|
|
92
|
+
) => ({
|
|
93
|
+
runtimeId: availableRuntimeIds[0],
|
|
94
|
+
reason: "test",
|
|
95
|
+
})
|
|
96
|
+
);
|
|
97
|
+
mockTestRuntimeConnection.mockImplementation((runtimeId: AgentRuntimeId) => {
|
|
98
|
+
if (runtimeId === "claude-code") {
|
|
99
|
+
return Promise.resolve({
|
|
100
|
+
connected: false,
|
|
101
|
+
error: "Claude Code process exited with code 1",
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return Promise.resolve({ connected: true });
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("falls back from an unavailable requested task runtime to a compatible alternate", async () => {
|
|
109
|
+
mockGetProfile.mockReturnValue({
|
|
110
|
+
id: "upgrade-assistant",
|
|
111
|
+
allowedTools: ["Bash(git status)", "Read", "Write"],
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const target = await resolveTaskExecutionTarget({
|
|
115
|
+
title: "Upgrade local branch",
|
|
116
|
+
description: "Merge upstream main safely",
|
|
117
|
+
requestedRuntimeId: "claude-code",
|
|
118
|
+
profileId: "upgrade-assistant",
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
expect(target.requestedRuntimeId).toBe("claude-code");
|
|
122
|
+
expect(target.effectiveRuntimeId).toBe("openai-codex-app-server");
|
|
123
|
+
expect(target.fallbackApplied).toBe(true);
|
|
124
|
+
expect(target.fallbackReason).toContain("Fell back to OpenAI Codex App Server");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("auto-selects a healthy runtime when no task runtime was requested", async () => {
|
|
128
|
+
mockGetProfile.mockReturnValue({
|
|
129
|
+
id: "general",
|
|
130
|
+
allowedTools: [],
|
|
131
|
+
preferredRuntime: "anthropic-direct",
|
|
132
|
+
});
|
|
133
|
+
mockSuggestRuntime.mockReturnValue({
|
|
134
|
+
runtimeId: "openai-codex-app-server",
|
|
135
|
+
reason: "test",
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const target = await resolveTaskExecutionTarget({
|
|
139
|
+
title: "Fix failing build",
|
|
140
|
+
description: "Debug and patch the repo",
|
|
141
|
+
profileId: "general",
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
expect(target.requestedRuntimeId).toBeNull();
|
|
145
|
+
expect(target.effectiveRuntimeId).toBe("openai-codex-app-server");
|
|
146
|
+
expect(target.fallbackApplied).toBe(false);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("falls back chat turns to the mapped alternate model when the requested runtime is unavailable", async () => {
|
|
150
|
+
const target = await resolveChatExecutionTarget({
|
|
151
|
+
requestedRuntimeId: "claude-code",
|
|
152
|
+
requestedModelId: "sonnet",
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
expect(target.requestedRuntimeId).toBe("claude-code");
|
|
156
|
+
expect(target.effectiveRuntimeId).toBe("openai-codex-app-server");
|
|
157
|
+
expect(target.effectiveModelId).toBe("gpt-5.3-codex");
|
|
158
|
+
expect(target.fallbackApplied).toBe(true);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("refuses resume when the last effective runtime is unavailable", async () => {
|
|
162
|
+
await expect(
|
|
163
|
+
resolveResumeExecutionTarget({
|
|
164
|
+
requestedRuntimeId: "claude-code",
|
|
165
|
+
effectiveRuntimeId: "claude-code",
|
|
166
|
+
})
|
|
167
|
+
).rejects.toThrow("Claude Code process exited with code 1");
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("throws a named error when no chat runtime is healthy", async () => {
|
|
171
|
+
mockTestRuntimeConnection.mockResolvedValue({
|
|
172
|
+
connected: false,
|
|
173
|
+
error: "all down",
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
await expect(
|
|
177
|
+
resolveChatExecutionTarget({
|
|
178
|
+
requestedRuntimeId: "claude-code",
|
|
179
|
+
requestedModelId: "sonnet",
|
|
180
|
+
})
|
|
181
|
+
).rejects.toBeInstanceOf(RequestedModelUnavailableError);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
@@ -481,6 +481,14 @@ async function executeAnthropicDirectTask(taskId: string, isResume = false): Pro
|
|
|
481
481
|
startedAt: usageState.startedAt,
|
|
482
482
|
finishedAt: new Date(),
|
|
483
483
|
});
|
|
484
|
+
|
|
485
|
+
await db
|
|
486
|
+
.update(tasks)
|
|
487
|
+
.set({
|
|
488
|
+
effectiveModelId: result.totalUsage.modelId ?? modelId,
|
|
489
|
+
updatedAt: new Date(),
|
|
490
|
+
})
|
|
491
|
+
.where(eq(tasks.id, taskId));
|
|
484
492
|
} catch (err) {
|
|
485
493
|
if (!abortController.signal.aborted) {
|
|
486
494
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -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
|
*
|