agentweaver 0.1.16 → 0.1.17
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 +50 -10
- package/dist/artifacts.js +73 -3
- package/dist/doctor/checks/executors.js +2 -2
- package/dist/flow-state.js +138 -1
- package/dist/index.js +175 -61
- package/dist/interactive/controller.js +56 -23
- package/dist/interactive/ink/index.js +22 -1
- package/dist/interactive/tree.js +2 -2
- package/dist/pipeline/auto-flow.js +9 -6
- package/dist/pipeline/context.js +6 -5
- package/dist/pipeline/declarative-flows.js +39 -20
- package/dist/pipeline/flow-catalog.js +36 -14
- package/dist/pipeline/flow-specs/auto-common.json +1 -0
- package/dist/pipeline/flow-specs/auto-golang.json +27 -1
- package/dist/pipeline/flow-specs/design-review/design-review-loop.json +13 -1
- package/dist/pipeline/flow-specs/plan.json +4 -2
- package/dist/pipeline/launch-profile-config.js +30 -18
- package/dist/pipeline/node-contract.js +1 -0
- package/dist/pipeline/node-registry.js +74 -5
- package/dist/pipeline/nodes/flow-run-node.js +188 -173
- package/dist/pipeline/nodes/llm-prompt-node.js +15 -33
- package/dist/pipeline/plugin-loader.js +389 -0
- package/dist/pipeline/plugin-types.js +1 -0
- package/dist/pipeline/registry.js +71 -4
- package/dist/pipeline/spec-compiler.js +1 -0
- package/dist/pipeline/spec-loader.js +14 -0
- package/dist/pipeline/spec-validator.js +6 -0
- package/dist/pipeline/value-resolver.js +2 -1
- package/dist/plugin-sdk.js +1 -0
- package/dist/runtime/artifact-registry.js +3 -0
- package/dist/runtime/execution-routing.js +25 -19
- package/dist/runtime/interactive-execution-routing.js +66 -57
- package/docs/example/.flows/examples/claude-example.json +50 -0
- package/docs/example/.plugins/claude-example-plugin/index.js +149 -0
- package/docs/example/.plugins/claude-example-plugin/plugin.json +8 -0
- package/docs/examples/.flows/claude-example.json +50 -0
- package/docs/examples/.plugins/claude-example-plugin/index.js +149 -0
- package/docs/examples/.plugins/claude-example-plugin/plugin.json +8 -0
- package/docs/plugin-sdk.md +731 -0
- package/package.json +6 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { TaskRunnerError } from "../errors.js";
|
|
3
|
-
import {
|
|
3
|
+
import { allowedModelsForExecutor, DEFAULT_LAUNCH_PROFILE, defaultModelForExecutor, isAllowedModelForExecutor, resolveLaunchProfile, } from "../pipeline/launch-profile-config.js";
|
|
4
4
|
import { BUILT_IN_EXECUTION_PRESET_IDS, EXECUTION_ROUTING_GROUPS, } from "../pipeline/execution-routing-config.js";
|
|
5
5
|
export const BUILT_IN_EXECUTION_PRESETS = {
|
|
6
6
|
balanced: {
|
|
@@ -62,12 +62,14 @@ function stableRoutingPayload(routing) {
|
|
|
62
62
|
export function executionRoutingFingerprint(routing) {
|
|
63
63
|
return createHash("sha256").update(stableRoutingPayload({ ...routing, fingerprint: "" })).digest("hex");
|
|
64
64
|
}
|
|
65
|
-
export function validateExecutionRoute(executor, model) {
|
|
66
|
-
|
|
67
|
-
if (!allowedModels) {
|
|
65
|
+
export function validateExecutionRoute(executor, model, executors) {
|
|
66
|
+
if (!executors && !isAllowedModelForExecutor(executor, model)) {
|
|
68
67
|
throw new TaskRunnerError(`Unsupported llm executor '${executor}'.`);
|
|
69
68
|
}
|
|
70
|
-
if (!
|
|
69
|
+
if (executors && !executors.getRouting(executor)) {
|
|
70
|
+
throw new TaskRunnerError(`Unsupported llm executor '${executor}'.`);
|
|
71
|
+
}
|
|
72
|
+
if (!isAllowedModelForExecutor(executor, model, executors)) {
|
|
71
73
|
throw new TaskRunnerError(`Model '${model}' is not allowed for executor '${executor}'.`);
|
|
72
74
|
}
|
|
73
75
|
}
|
|
@@ -77,25 +79,25 @@ export function toExecutionRouteSelection(route) {
|
|
|
77
79
|
model: route.model,
|
|
78
80
|
};
|
|
79
81
|
}
|
|
80
|
-
export function resolveExecutionRoute(selection, fallback) {
|
|
82
|
+
export function resolveExecutionRoute(selection, fallback, executors) {
|
|
81
83
|
const launchProfile = resolveLaunchProfile({
|
|
82
84
|
executor: selection.executor,
|
|
83
85
|
model: selection.model,
|
|
84
|
-
}, fallback);
|
|
85
|
-
validateExecutionRoute(launchProfile.executor, launchProfile.model);
|
|
86
|
+
}, fallback, executors);
|
|
87
|
+
validateExecutionRoute(launchProfile.executor, launchProfile.model, executors);
|
|
86
88
|
return launchProfile;
|
|
87
89
|
}
|
|
88
90
|
export function resolveExecutionRouting(options) {
|
|
89
91
|
const fallbackDefaultRoute = options.fallbackDefaultRoute ?? DEFAULT_LAUNCH_PROFILE;
|
|
90
92
|
const preset = options.presetId ? BUILT_IN_EXECUTION_PRESETS[options.presetId] : null;
|
|
91
93
|
const defaultRouteSelection = options.defaultRoute ?? preset?.defaultRoute ?? toExecutionRouteSelection(fallbackDefaultRoute);
|
|
92
|
-
const defaultRoute = resolveExecutionRoute(defaultRouteSelection, fallbackDefaultRoute);
|
|
94
|
+
const defaultRoute = resolveExecutionRoute(defaultRouteSelection, fallbackDefaultRoute, options.executors);
|
|
93
95
|
const groups = Object.fromEntries(EXECUTION_ROUTING_GROUPS.map((group) => {
|
|
94
96
|
const override = options.currentRunOverrides?.[group]
|
|
95
97
|
?? options.presetOverrides?.[group]
|
|
96
98
|
?? preset?.groupOverrides?.[group]
|
|
97
99
|
?? { executor: "default", model: "default" };
|
|
98
|
-
return [group, resolveExecutionRoute(override, defaultRoute)];
|
|
100
|
+
return [group, resolveExecutionRoute(override, defaultRoute, options.executors)];
|
|
99
101
|
}));
|
|
100
102
|
const fingerprint = executionRoutingFingerprint({
|
|
101
103
|
defaultRoute,
|
|
@@ -143,18 +145,18 @@ export function executorsForRoutingGroups(routing, groups) {
|
|
|
143
145
|
}
|
|
144
146
|
return requiredExecutors;
|
|
145
147
|
}
|
|
146
|
-
export function normalizeEditableExecutionRouting(routes) {
|
|
148
|
+
export function normalizeEditableExecutionRouting(routes, executors) {
|
|
147
149
|
const normalizedRoutes = {};
|
|
148
150
|
const validationErrors = [];
|
|
149
151
|
for (const group of EXECUTION_ROUTING_GROUPS) {
|
|
150
152
|
const route = routes[group];
|
|
151
|
-
if (isAllowedModelForExecutor(route.executor, route.model)) {
|
|
153
|
+
if (isAllowedModelForExecutor(route.executor, route.model, executors)) {
|
|
152
154
|
normalizedRoutes[group] = { ...route };
|
|
153
155
|
continue;
|
|
154
156
|
}
|
|
155
157
|
normalizedRoutes[group] = {
|
|
156
158
|
executor: route.executor,
|
|
157
|
-
model: defaultModelForExecutor(route.executor),
|
|
159
|
+
model: defaultModelForExecutor(route.executor, executors),
|
|
158
160
|
};
|
|
159
161
|
validationErrors.push(`${routingGroupLabel(group)} model '${route.model}' is not allowed for executor '${route.executor}'. Select a ${route.executor} model.`);
|
|
160
162
|
}
|
|
@@ -184,10 +186,14 @@ export function defaultExecutionRouting() {
|
|
|
184
186
|
},
|
|
185
187
|
});
|
|
186
188
|
}
|
|
187
|
-
export function resolveStoredExecutionRoutingSnapshot(routing) {
|
|
188
|
-
|
|
189
|
+
export function resolveStoredExecutionRoutingSnapshot(routing, executors) {
|
|
190
|
+
if (executors) {
|
|
191
|
+
validateExecutionRoute(routing.defaultRoute.executor, routing.defaultRoute.model, executors);
|
|
192
|
+
}
|
|
189
193
|
for (const group of EXECUTION_ROUTING_GROUPS) {
|
|
190
|
-
|
|
194
|
+
if (executors) {
|
|
195
|
+
validateExecutionRoute(routing.groups[group].executor, routing.groups[group].model, executors);
|
|
196
|
+
}
|
|
191
197
|
}
|
|
192
198
|
const fingerprint = executionRoutingFingerprint({
|
|
193
199
|
defaultRoute: routing.defaultRoute,
|
|
@@ -218,9 +224,9 @@ export function singleLaunchProfileExecutionRouting(launchProfile) {
|
|
|
218
224
|
currentRunOverrides: Object.fromEntries(EXECUTION_ROUTING_GROUPS.map((group) => [group, toExecutionRouteSelection(launchProfile)])),
|
|
219
225
|
});
|
|
220
226
|
}
|
|
221
|
-
export function modelOptionsForExecutor(executor) {
|
|
222
|
-
const defaultModel = defaultModelForExecutor(executor);
|
|
223
|
-
return
|
|
227
|
+
export function modelOptionsForExecutor(executor, executors) {
|
|
228
|
+
const defaultModel = defaultModelForExecutor(executor, executors);
|
|
229
|
+
return allowedModelsForExecutor(executor, executors).map((model) => ({
|
|
224
230
|
value: model,
|
|
225
231
|
label: model === defaultModel ? `${model} [default]` : model,
|
|
226
232
|
}));
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { flowRoutingGroups, flowRoutingKey } from "../pipeline/flow-catalog.js";
|
|
3
3
|
import { loadNamedDeclarativeFlow } from "../pipeline/declarative-flows.js";
|
|
4
|
+
import { createPipelineRegistryContext } from "../pipeline/plugin-loader.js";
|
|
4
5
|
import { EXECUTION_ROUTING_GROUPS, } from "../pipeline/execution-routing-config.js";
|
|
5
6
|
import { DEFAULT_LAUNCH_PROFILE, defaultModelForExecutor, isAllowedModelForExecutor, } from "../pipeline/launch-profile-config.js";
|
|
6
7
|
import { FlowInterruptedError, TaskRunnerError } from "../errors.js";
|
|
@@ -58,15 +59,9 @@ function executionPresetSelectionForm(flowEntry, flowDefaultAvailable, lastUsedA
|
|
|
58
59
|
],
|
|
59
60
|
};
|
|
60
61
|
}
|
|
61
|
-
function
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
{ value: "opencode", label: "opencode" },
|
|
65
|
-
];
|
|
66
|
-
}
|
|
67
|
-
function selectedExecutorFromValues(values, fieldId, fallbackExecutor) {
|
|
68
|
-
const candidate = values[fieldId];
|
|
69
|
-
return candidate === "codex" || candidate === "opencode" ? candidate : fallbackExecutor;
|
|
62
|
+
function selectedExecutorFromValues(values, fieldId, fallbackExecutor, registryContext) {
|
|
63
|
+
const candidate = typeof values[fieldId] === "string" ? values[fieldId] : "";
|
|
64
|
+
return registryContext.executors.getRouting(candidate)?.kind === "llm" ? candidate : fallbackExecutor;
|
|
70
65
|
}
|
|
71
66
|
function routingEditorDraftFromRouting(routing) {
|
|
72
67
|
return {
|
|
@@ -83,7 +78,11 @@ function routingEditorDraftFromRouting(routing) {
|
|
|
83
78
|
])),
|
|
84
79
|
};
|
|
85
80
|
}
|
|
86
|
-
function advancedRoutingEditorForm(activeGroups, draft, validationMessage) {
|
|
81
|
+
function advancedRoutingEditorForm(activeGroups, draft, registryContext, validationMessage) {
|
|
82
|
+
const executorOptions = registryContext.executors.llmExecutors().map((entry) => ({
|
|
83
|
+
value: entry.id,
|
|
84
|
+
label: entry.id,
|
|
85
|
+
}));
|
|
87
86
|
const fields = [
|
|
88
87
|
{
|
|
89
88
|
id: "default_route_executor",
|
|
@@ -91,7 +90,7 @@ function advancedRoutingEditorForm(activeGroups, draft, validationMessage) {
|
|
|
91
90
|
label: "Default route executor",
|
|
92
91
|
required: true,
|
|
93
92
|
default: draft.defaultRoute.executor,
|
|
94
|
-
options:
|
|
93
|
+
options: executorOptions,
|
|
95
94
|
},
|
|
96
95
|
{
|
|
97
96
|
id: "default_route_model",
|
|
@@ -100,8 +99,8 @@ function advancedRoutingEditorForm(activeGroups, draft, validationMessage) {
|
|
|
100
99
|
help: "Available models follow the selected default executor.",
|
|
101
100
|
required: true,
|
|
102
101
|
default: draft.defaultRoute.model,
|
|
103
|
-
options: modelOptionsForExecutor(draft.defaultRoute.executor),
|
|
104
|
-
optionsFromValues: (values) => modelOptionsForExecutor(selectedExecutorFromValues(values, "default_route_executor", draft.defaultRoute.executor)),
|
|
102
|
+
options: modelOptionsForExecutor(draft.defaultRoute.executor, registryContext.executors),
|
|
103
|
+
optionsFromValues: (values) => modelOptionsForExecutor(selectedExecutorFromValues(values, "default_route_executor", draft.defaultRoute.executor, registryContext), registryContext.executors),
|
|
105
104
|
},
|
|
106
105
|
...activeGroups.flatMap((group) => {
|
|
107
106
|
const route = draft.groups[group];
|
|
@@ -112,7 +111,7 @@ function advancedRoutingEditorForm(activeGroups, draft, validationMessage) {
|
|
|
112
111
|
label: `${routingGroupLabel(group)} executor`,
|
|
113
112
|
required: true,
|
|
114
113
|
default: route.executor,
|
|
115
|
-
options:
|
|
114
|
+
options: executorOptions,
|
|
116
115
|
},
|
|
117
116
|
{
|
|
118
117
|
id: `${group}_model`,
|
|
@@ -121,8 +120,8 @@ function advancedRoutingEditorForm(activeGroups, draft, validationMessage) {
|
|
|
121
120
|
help: `Available models follow the selected ${routingGroupLabel(group)} executor.`,
|
|
122
121
|
required: true,
|
|
123
122
|
default: route.model,
|
|
124
|
-
options: modelOptionsForExecutor(route.executor),
|
|
125
|
-
optionsFromValues: (values) => modelOptionsForExecutor(selectedExecutorFromValues(values, `${group}_executor`, route.executor)),
|
|
123
|
+
options: modelOptionsForExecutor(route.executor, registryContext.executors),
|
|
124
|
+
optionsFromValues: (values) => modelOptionsForExecutor(selectedExecutorFromValues(values, `${group}_executor`, route.executor, registryContext), registryContext.executors),
|
|
126
125
|
},
|
|
127
126
|
];
|
|
128
127
|
}),
|
|
@@ -202,8 +201,8 @@ function presetNameForm() {
|
|
|
202
201
|
],
|
|
203
202
|
};
|
|
204
203
|
}
|
|
205
|
-
function normalizeEditableRoute(label, route) {
|
|
206
|
-
if (isAllowedModelForExecutor(route.executor, route.model)) {
|
|
204
|
+
function normalizeEditableRoute(label, route, executors) {
|
|
205
|
+
if (isAllowedModelForExecutor(route.executor, route.model, executors)) {
|
|
207
206
|
return {
|
|
208
207
|
route: { ...route },
|
|
209
208
|
validationErrors: [],
|
|
@@ -212,20 +211,20 @@ function normalizeEditableRoute(label, route) {
|
|
|
212
211
|
return {
|
|
213
212
|
route: {
|
|
214
213
|
executor: route.executor,
|
|
215
|
-
model: defaultModelForExecutor(route.executor),
|
|
214
|
+
model: defaultModelForExecutor(route.executor, executors),
|
|
216
215
|
},
|
|
217
216
|
validationErrors: [
|
|
218
217
|
`${label} model '${route.model}' is not allowed for executor '${route.executor}'. Select a ${route.executor} model.`,
|
|
219
218
|
],
|
|
220
219
|
};
|
|
221
220
|
}
|
|
222
|
-
function normalizeEditableRoutingDraft(draft) {
|
|
223
|
-
const defaultRoute = normalizeEditableRoute("Default route", draft.defaultRoute);
|
|
221
|
+
function normalizeEditableRoutingDraft(draft, executors) {
|
|
222
|
+
const defaultRoute = normalizeEditableRoute("Default route", draft.defaultRoute, executors);
|
|
224
223
|
const groups = {};
|
|
225
224
|
const currentRunOverrides = {};
|
|
226
225
|
const validationErrors = [...defaultRoute.validationErrors];
|
|
227
226
|
for (const group of EXECUTION_ROUTING_GROUPS) {
|
|
228
|
-
const normalizedRoute = normalizeEditableRoute(routingGroupLabel(group), draft.groups[group]);
|
|
227
|
+
const normalizedRoute = normalizeEditableRoute(routingGroupLabel(group), draft.groups[group], executors);
|
|
229
228
|
groups[group] = normalizedRoute.route;
|
|
230
229
|
if (normalizedRoute.route.executor !== defaultRoute.route.executor
|
|
231
230
|
|| normalizedRoute.route.model !== defaultRoute.route.model) {
|
|
@@ -274,40 +273,47 @@ function formatAsciiTable(headers, rows, maxWidths) {
|
|
|
274
273
|
const separator = `|-${widths.map((width) => "-".repeat(width)).join("-|-")}-|`;
|
|
275
274
|
return [formatRow(headers), separator, ...rows.map((row) => formatRow(row))];
|
|
276
275
|
}
|
|
277
|
-
function collectEffectiveRoutedStepRows(flow, cwd, routing, prefixSegments = [], ancestry = []) {
|
|
276
|
+
function collectEffectiveRoutedStepRows(flow, cwd, routing, registryContext, prefixSegments = [], ancestry = []) {
|
|
278
277
|
if (ancestry.includes(flow.absolutePath)) {
|
|
279
|
-
return [];
|
|
278
|
+
return Promise.resolve([]);
|
|
280
279
|
}
|
|
281
280
|
const nextAncestry = [...ancestry, flow.absolutePath];
|
|
282
281
|
const rows = [];
|
|
283
|
-
|
|
284
|
-
for (const
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
282
|
+
const run = async () => {
|
|
283
|
+
for (const phase of flow.phases) {
|
|
284
|
+
for (const step of phase.steps) {
|
|
285
|
+
const stepRef = `${phase.id}.${step.id}`;
|
|
286
|
+
if (step.routingGroup) {
|
|
287
|
+
const route = routing.groups[step.routingGroup];
|
|
288
|
+
rows.push({
|
|
289
|
+
step: [...prefixSegments, stepRef].join(" > "),
|
|
290
|
+
group: routingGroupLabel(step.routingGroup),
|
|
291
|
+
executor: route.executor,
|
|
292
|
+
model: route.model,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
if (step.node !== "flow-run") {
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
const nestedFlowName = step.params?.fileName;
|
|
299
|
+
if (!nestedFlowName || !("const" in nestedFlowName) || typeof nestedFlowName.const !== "string") {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
const nestedFlow = await loadNamedDeclarativeFlow(nestedFlowName.const, cwd, {
|
|
303
|
+
...(registryContext ? { registryContext } : {}),
|
|
293
304
|
});
|
|
305
|
+
const nestedFlowLabel = path.basename(nestedFlow.fileName, path.extname(nestedFlow.fileName));
|
|
306
|
+
rows.push(...await collectEffectiveRoutedStepRows(nestedFlow, cwd, routing, registryContext, [...prefixSegments, stepRef, nestedFlowLabel], nextAncestry));
|
|
294
307
|
}
|
|
295
|
-
if (step.node !== "flow-run") {
|
|
296
|
-
continue;
|
|
297
|
-
}
|
|
298
|
-
const nestedFlowName = step.params?.fileName;
|
|
299
|
-
if (!nestedFlowName || !("const" in nestedFlowName) || typeof nestedFlowName.const !== "string") {
|
|
300
|
-
continue;
|
|
301
|
-
}
|
|
302
|
-
const nestedFlow = loadNamedDeclarativeFlow(nestedFlowName.const, cwd);
|
|
303
|
-
const nestedFlowLabel = path.basename(nestedFlow.fileName, path.extname(nestedFlow.fileName));
|
|
304
|
-
rows.push(...collectEffectiveRoutedStepRows(nestedFlow, cwd, routing, [...prefixSegments, stepRef, nestedFlowLabel], nextAncestry));
|
|
305
308
|
}
|
|
306
|
-
|
|
307
|
-
|
|
309
|
+
return rows;
|
|
310
|
+
};
|
|
311
|
+
return run();
|
|
308
312
|
}
|
|
309
|
-
export function describeEffectiveRoutingPreview(flowEntry, routing, cwd) {
|
|
310
|
-
const previewGroups = flowRoutingGroups(flowEntry, cwd
|
|
313
|
+
export async function describeEffectiveRoutingPreview(flowEntry, routing, cwd, registryContext) {
|
|
314
|
+
const previewGroups = await flowRoutingGroups(flowEntry, cwd, {
|
|
315
|
+
...(registryContext ? { registryContext } : {}),
|
|
316
|
+
});
|
|
311
317
|
const summaryRows = [
|
|
312
318
|
["Default", routing.defaultRoute.executor, routing.defaultRoute.model],
|
|
313
319
|
...previewGroups.map((group) => [
|
|
@@ -320,7 +326,7 @@ export function describeEffectiveRoutingPreview(flowEntry, routing, cwd) {
|
|
|
320
326
|
"Effective routes:",
|
|
321
327
|
...formatAsciiTable(["Scope", "Executor", "Model"], summaryRows, [18, 12, 36]),
|
|
322
328
|
];
|
|
323
|
-
const routedSteps = collapseRepeatedEffectiveRoutedStepRows(collectEffectiveRoutedStepRows(flowEntry.flow, cwd, routing));
|
|
329
|
+
const routedSteps = collapseRepeatedEffectiveRoutedStepRows(await collectEffectiveRoutedStepRows(flowEntry.flow, cwd, routing, registryContext));
|
|
324
330
|
if (routedSteps.length === 0) {
|
|
325
331
|
return lines.join("\n");
|
|
326
332
|
}
|
|
@@ -332,7 +338,8 @@ export function describeEffectiveRoutingPreview(flowEntry, routing, cwd) {
|
|
|
332
338
|
].join("\n");
|
|
333
339
|
}
|
|
334
340
|
export async function requestInteractiveExecutionRouting(flowEntry, requestUserInput) {
|
|
335
|
-
const
|
|
341
|
+
const registryContext = await createPipelineRegistryContext(process.cwd());
|
|
342
|
+
const previewGroups = await flowRoutingGroups(flowEntry, process.cwd(), { registryContext });
|
|
336
343
|
const flowKey = flowRoutingKey(flowEntry);
|
|
337
344
|
const namedPresets = getNamedExecutionPresets();
|
|
338
345
|
const flowDefault = getFlowDefaultExecutionRouting(flowKey);
|
|
@@ -346,14 +353,14 @@ export async function requestInteractiveExecutionRouting(flowEntry, requestUserI
|
|
|
346
353
|
throw new TaskRunnerError("Flow default routing is unavailable.");
|
|
347
354
|
}
|
|
348
355
|
selectedPreset = { kind: "flow-default", label: "Flow default" };
|
|
349
|
-
routing = resolveStoredExecutionRoutingSnapshot(flowDefault.routing);
|
|
356
|
+
routing = resolveStoredExecutionRoutingSnapshot(flowDefault.routing, registryContext.executors);
|
|
350
357
|
}
|
|
351
358
|
else if (selectedPresetValue === "last-used") {
|
|
352
359
|
if (!lastUsed) {
|
|
353
360
|
throw new TaskRunnerError("Last-used routing is unavailable.");
|
|
354
361
|
}
|
|
355
362
|
selectedPreset = { kind: "last-used", label: "Last used" };
|
|
356
|
-
routing = resolveStoredExecutionRoutingSnapshot(lastUsed.routing);
|
|
363
|
+
routing = resolveStoredExecutionRoutingSnapshot(lastUsed.routing, registryContext.executors);
|
|
357
364
|
}
|
|
358
365
|
else if (selectedPresetValue.startsWith("named:")) {
|
|
359
366
|
const presetName = selectedPresetValue.slice("named:".length);
|
|
@@ -362,7 +369,7 @@ export async function requestInteractiveExecutionRouting(flowEntry, requestUserI
|
|
|
362
369
|
throw new TaskRunnerError(`Named preset '${presetName}' is unavailable.`);
|
|
363
370
|
}
|
|
364
371
|
selectedPreset = { kind: "named", presetId: presetName, label: `Named preset: ${presetName}` };
|
|
365
|
-
routing = resolveStoredExecutionRoutingSnapshot(namedPreset.routing);
|
|
372
|
+
routing = resolveStoredExecutionRoutingSnapshot(namedPreset.routing, registryContext.executors);
|
|
366
373
|
}
|
|
367
374
|
else if (selectedPresetValue.startsWith("built-in:")) {
|
|
368
375
|
const presetId = selectedPresetValue.slice("built-in:".length);
|
|
@@ -371,11 +378,12 @@ export async function requestInteractiveExecutionRouting(flowEntry, requestUserI
|
|
|
371
378
|
throw new TaskRunnerError(`Unknown execution preset '${presetId}'.`);
|
|
372
379
|
}
|
|
373
380
|
selectedPreset = { kind: "built-in", presetId, label: preset.label };
|
|
374
|
-
routing = resolveExecutionRouting({ presetId });
|
|
381
|
+
routing = resolveExecutionRouting({ presetId, executors: registryContext.executors });
|
|
375
382
|
}
|
|
376
383
|
else {
|
|
377
384
|
selectedPreset = { kind: "custom", label: "Custom" };
|
|
378
385
|
routing = resolveExecutionRouting({
|
|
386
|
+
executors: registryContext.executors,
|
|
379
387
|
defaultRoute: {
|
|
380
388
|
executor: DEFAULT_LAUNCH_PROFILE.executor,
|
|
381
389
|
model: DEFAULT_LAUNCH_PROFILE.model,
|
|
@@ -385,7 +393,7 @@ export async function requestInteractiveExecutionRouting(flowEntry, requestUserI
|
|
|
385
393
|
let editorDraft = routingEditorDraftFromRouting(routing);
|
|
386
394
|
let editorValidationMessage;
|
|
387
395
|
for (;;) {
|
|
388
|
-
const previewText = `Preset: ${selectedPreset.label}\n${describeEffectiveRoutingPreview(flowEntry, routing, process.cwd())}`;
|
|
396
|
+
const previewText = `Preset: ${selectedPreset.label}\n${await describeEffectiveRoutingPreview(flowEntry, routing, process.cwd(), registryContext)}`;
|
|
389
397
|
const actionResult = await requestUserInput(routingActionForm(previewText));
|
|
390
398
|
const action = String(actionResult.values.action ?? "start");
|
|
391
399
|
if (action === "cancel") {
|
|
@@ -395,7 +403,7 @@ export async function requestInteractiveExecutionRouting(flowEntry, requestUserI
|
|
|
395
403
|
saveLastUsedExecutionRouting(flowKey, routing, selectedPreset);
|
|
396
404
|
return { routing, selectedPreset };
|
|
397
405
|
}
|
|
398
|
-
const routingFormResult = await requestUserInput(advancedRoutingEditorForm(previewGroups, editorDraft, editorValidationMessage));
|
|
406
|
+
const routingFormResult = await requestUserInput(advancedRoutingEditorForm(previewGroups, editorDraft, registryContext, editorValidationMessage));
|
|
399
407
|
const requestedDefaultRoute = {
|
|
400
408
|
executor: String(routingFormResult.values.default_route_executor ?? editorDraft.defaultRoute.executor),
|
|
401
409
|
model: String(routingFormResult.values.default_route_model ?? editorDraft.defaultRoute.model),
|
|
@@ -418,7 +426,7 @@ export async function requestInteractiveExecutionRouting(flowEntry, requestUserI
|
|
|
418
426
|
];
|
|
419
427
|
})),
|
|
420
428
|
};
|
|
421
|
-
const normalizedDraft = normalizeEditableRoutingDraft(requestedDraft);
|
|
429
|
+
const normalizedDraft = normalizeEditableRoutingDraft(requestedDraft, registryContext.executors);
|
|
422
430
|
editorDraft = normalizedDraft.draft;
|
|
423
431
|
if (normalizedDraft.validationErrors.length > 0) {
|
|
424
432
|
editorValidationMessage = normalizedDraft.validationErrors.join("\n");
|
|
@@ -426,6 +434,7 @@ export async function requestInteractiveExecutionRouting(flowEntry, requestUserI
|
|
|
426
434
|
}
|
|
427
435
|
try {
|
|
428
436
|
routing = resolveExecutionRouting({
|
|
437
|
+
executors: registryContext.executors,
|
|
429
438
|
defaultRoute: {
|
|
430
439
|
executor: normalizedDraft.draft.defaultRoute.executor,
|
|
431
440
|
model: normalizedDraft.draft.defaultRoute.model,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"kind": "claude-example-flow",
|
|
3
|
+
"version": 1,
|
|
4
|
+
"description": "Project-local reference flow for the Claude example plugin.",
|
|
5
|
+
"phases": [
|
|
6
|
+
{
|
|
7
|
+
"id": "example",
|
|
8
|
+
"steps": [
|
|
9
|
+
{
|
|
10
|
+
"id": "run_claude_prompt",
|
|
11
|
+
"node": "llm-prompt",
|
|
12
|
+
"prompt": {
|
|
13
|
+
"inlineTemplate": "Use the current workspace. Create the JSON file `.agentweaver/.artifacts/examples/claude-example-proof.json` with exactly this shape: `{ \"status\": \"ok\", \"executor\": \"claude\", \"message\": \"<one short sentence proving the Claude example plugin executed through llm-prompt>\", \"model\": \"<the exact model name you used, or an empty string if unavailable>\" }`. Write valid JSON only to that file. After the file is written, reply with exactly `wrote .agentweaver/.artifacts/examples/claude-example-proof.json`."
|
|
14
|
+
},
|
|
15
|
+
"params": {
|
|
16
|
+
"labelText": {
|
|
17
|
+
"const": "Run Claude via llm-prompt"
|
|
18
|
+
},
|
|
19
|
+
"executor": {
|
|
20
|
+
"const": "claude"
|
|
21
|
+
},
|
|
22
|
+
"requiredArtifacts": {
|
|
23
|
+
"list": [
|
|
24
|
+
{
|
|
25
|
+
"const": ".agentweaver/.artifacts/examples/claude-example-proof.json"
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"missingArtifactsMessage": {
|
|
30
|
+
"const": "The Claude example flow requires Claude to write the fixed proof artifact."
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"expect": [
|
|
34
|
+
{
|
|
35
|
+
"kind": "require-artifacts",
|
|
36
|
+
"paths": {
|
|
37
|
+
"list": [
|
|
38
|
+
{
|
|
39
|
+
"const": ".agentweaver/.artifacts/examples/claude-example-proof.json"
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
"message": "The Claude example flow must write the fixed proof artifact."
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { AGENTWEAVER_PLUGIN_SDK_VERSION } from "agentweaver/plugin-sdk";
|
|
2
|
+
|
|
3
|
+
export const CLAUDE_EXAMPLE_PLUGIN_SDK_VERSION = AGENTWEAVER_PLUGIN_SDK_VERSION;
|
|
4
|
+
export const CLAUDE_EXECUTOR_ID = "claude";
|
|
5
|
+
export const CLAUDE_AUTH_STATUS_ERROR = "Claude CLI authentication is required. Run 'claude auth status' and sign in before using the example flow.";
|
|
6
|
+
export const CLAUDE_NORMALIZATION_ERROR =
|
|
7
|
+
"Claude JSON normalization failed: no supported assistant text was found in result, message.content[*].text, or content[*].text.";
|
|
8
|
+
|
|
9
|
+
function nonEmptyString(value) {
|
|
10
|
+
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function resolveSetting(override, env, envKey, defaultValue) {
|
|
14
|
+
const direct = nonEmptyString(typeof override === "number" ? String(override) : override);
|
|
15
|
+
if (direct) {
|
|
16
|
+
return direct;
|
|
17
|
+
}
|
|
18
|
+
const fromEnv = nonEmptyString(env?.[envKey]);
|
|
19
|
+
if (fromEnv) {
|
|
20
|
+
return fromEnv;
|
|
21
|
+
}
|
|
22
|
+
return nonEmptyString(defaultValue);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function resolveStringListSetting(override, env, envKey, defaultValue) {
|
|
26
|
+
const direct = nonEmptyString(override);
|
|
27
|
+
const envValue = nonEmptyString(env?.[envKey]);
|
|
28
|
+
const effective = direct ?? envValue ?? nonEmptyString(defaultValue);
|
|
29
|
+
if (!effective) {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
return effective
|
|
33
|
+
.split(/[,\s]+/)
|
|
34
|
+
.map((item) => item.trim())
|
|
35
|
+
.filter((item) => item.length > 0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function extractTextFragments(items) {
|
|
39
|
+
if (!Array.isArray(items)) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const fragments = items
|
|
43
|
+
.map((item) => (item && typeof item === "object" ? item.text : undefined))
|
|
44
|
+
.filter((text) => typeof text === "string" && text.trim().length > 0);
|
|
45
|
+
return fragments.length > 0 ? fragments.join("\n") : null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function normalizeClaudePayload(payload, effectiveModel = "") {
|
|
49
|
+
const resultText = nonEmptyString(payload?.result);
|
|
50
|
+
const messageContentText = extractTextFragments(payload?.message?.content);
|
|
51
|
+
const contentText = extractTextFragments(payload?.content);
|
|
52
|
+
const output = resultText ?? messageContentText ?? contentText;
|
|
53
|
+
if (!output) {
|
|
54
|
+
throw new Error(CLAUDE_NORMALIZATION_ERROR);
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
output,
|
|
58
|
+
model: nonEmptyString(payload?.model) ?? effectiveModel,
|
|
59
|
+
rawResponse: payload,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function verifyClaudeAuth(runtime, env, config) {
|
|
64
|
+
const command = runtime.resolveCmd(config.defaultCommand, config.commandEnvVar);
|
|
65
|
+
try {
|
|
66
|
+
await runtime.runCommand([command, "auth", "status"], {
|
|
67
|
+
env,
|
|
68
|
+
label: "claude:auth-status",
|
|
69
|
+
printFailureOutput: false,
|
|
70
|
+
});
|
|
71
|
+
} catch {
|
|
72
|
+
throw new Error(CLAUDE_AUTH_STATUS_ERROR);
|
|
73
|
+
}
|
|
74
|
+
return command;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const claudeExecutorDefinition = {
|
|
78
|
+
kind: CLAUDE_EXECUTOR_ID,
|
|
79
|
+
version: 1,
|
|
80
|
+
defaultConfig: {
|
|
81
|
+
commandEnvVar: "CLAUDE_BIN",
|
|
82
|
+
defaultCommand: "claude",
|
|
83
|
+
modelEnvVar: "CLAUDE_MODEL",
|
|
84
|
+
defaultModel: "",
|
|
85
|
+
maxTurnsEnvVar: "CLAUDE_MAX_TURNS",
|
|
86
|
+
defaultMaxTurns: "",
|
|
87
|
+
permissionModeEnvVar: "CLAUDE_PERMISSION_MODE",
|
|
88
|
+
defaultPermissionMode: "bypassPermissions",
|
|
89
|
+
allowedToolsEnvVar: "CLAUDE_ALLOWED_TOOLS",
|
|
90
|
+
defaultAllowedTools: "",
|
|
91
|
+
disallowedToolsEnvVar: "CLAUDE_DISALLOWED_TOOLS",
|
|
92
|
+
defaultDisallowedTools: "",
|
|
93
|
+
addCwdAsAllowedDir: true,
|
|
94
|
+
},
|
|
95
|
+
async execute(context, input, config) {
|
|
96
|
+
const env = input?.env ?? context.env;
|
|
97
|
+
const command = input?.command ?? context.runtime.resolveCmd(config.defaultCommand, config.commandEnvVar);
|
|
98
|
+
const effectiveModel = resolveSetting(input?.model, env, config.modelEnvVar, config.defaultModel);
|
|
99
|
+
const effectiveMaxTurns = resolveSetting(input?.maxTurns, env, config.maxTurnsEnvVar, config.defaultMaxTurns);
|
|
100
|
+
const permissionMode = resolveSetting(undefined, env, config.permissionModeEnvVar, config.defaultPermissionMode);
|
|
101
|
+
const allowedTools = resolveStringListSetting(undefined, env, config.allowedToolsEnvVar, config.defaultAllowedTools);
|
|
102
|
+
const disallowedTools = resolveStringListSetting(undefined, env, config.disallowedToolsEnvVar, config.defaultDisallowedTools);
|
|
103
|
+
const argv = [
|
|
104
|
+
command,
|
|
105
|
+
"-p",
|
|
106
|
+
String(input?.prompt ?? ""),
|
|
107
|
+
"--output-format",
|
|
108
|
+
"json",
|
|
109
|
+
...(config.addCwdAsAllowedDir ? ["--add-dir", context.cwd] : []),
|
|
110
|
+
...(permissionMode ? ["--permission-mode", permissionMode] : []),
|
|
111
|
+
...(allowedTools.length > 0 ? ["--allowedTools", ...allowedTools] : []),
|
|
112
|
+
...(disallowedTools.length > 0 ? ["--disallowedTools", ...disallowedTools] : []),
|
|
113
|
+
...(effectiveModel ? ["--model", effectiveModel] : []),
|
|
114
|
+
...(effectiveMaxTurns ? ["--max-turns", effectiveMaxTurns] : []),
|
|
115
|
+
];
|
|
116
|
+
const stdout = await context.runtime.runCommand(argv, {
|
|
117
|
+
env,
|
|
118
|
+
dryRun: context.dryRun,
|
|
119
|
+
verbose: context.verbose,
|
|
120
|
+
label: `claude:${effectiveModel || "default"}`,
|
|
121
|
+
printFailureOutput: true,
|
|
122
|
+
});
|
|
123
|
+
let payload;
|
|
124
|
+
try {
|
|
125
|
+
payload = JSON.parse(stdout);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
throw new Error(`Claude CLI returned invalid JSON: ${error.message}`);
|
|
128
|
+
}
|
|
129
|
+
const normalized = normalizeClaudePayload(payload, effectiveModel ?? "");
|
|
130
|
+
return {
|
|
131
|
+
output: normalized.output,
|
|
132
|
+
command,
|
|
133
|
+
model: normalized.model ?? "",
|
|
134
|
+
rawResponse: normalized.rawResponse,
|
|
135
|
+
};
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export const executors = [
|
|
140
|
+
{
|
|
141
|
+
id: CLAUDE_EXECUTOR_ID,
|
|
142
|
+
definition: claudeExecutorDefinition,
|
|
143
|
+
routing: {
|
|
144
|
+
kind: "llm",
|
|
145
|
+
defaultModel: "sonnet",
|
|
146
|
+
models: ["sonnet", "opus", "haiku"],
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
];
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"kind": "claude-example-flow",
|
|
3
|
+
"version": 1,
|
|
4
|
+
"description": "Project-local reference flow for the Claude example plugin.",
|
|
5
|
+
"phases": [
|
|
6
|
+
{
|
|
7
|
+
"id": "example",
|
|
8
|
+
"steps": [
|
|
9
|
+
{
|
|
10
|
+
"id": "run_claude_prompt",
|
|
11
|
+
"node": "llm-prompt",
|
|
12
|
+
"prompt": {
|
|
13
|
+
"inlineTemplate": "Use the current workspace. Create the JSON file `.agentweaver/.artifacts/examples/claude-example-proof.json` with exactly this shape: `{ \"status\": \"ok\", \"executor\": \"claude\", \"message\": \"<one short sentence proving the Claude example plugin executed through llm-prompt>\", \"model\": \"<the exact model name you used, or an empty string if unavailable>\" }`. Write valid JSON only to that file. After the file is written, reply with exactly `wrote .agentweaver/.artifacts/examples/claude-example-proof.json`."
|
|
14
|
+
},
|
|
15
|
+
"params": {
|
|
16
|
+
"labelText": {
|
|
17
|
+
"const": "Run Claude via llm-prompt"
|
|
18
|
+
},
|
|
19
|
+
"executor": {
|
|
20
|
+
"const": "claude"
|
|
21
|
+
},
|
|
22
|
+
"requiredArtifacts": {
|
|
23
|
+
"list": [
|
|
24
|
+
{
|
|
25
|
+
"const": ".agentweaver/.artifacts/examples/claude-example-proof.json"
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"missingArtifactsMessage": {
|
|
30
|
+
"const": "The Claude example flow requires Claude to write the fixed proof artifact."
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"expect": [
|
|
34
|
+
{
|
|
35
|
+
"kind": "require-artifacts",
|
|
36
|
+
"paths": {
|
|
37
|
+
"list": [
|
|
38
|
+
{
|
|
39
|
+
"const": ".agentweaver/.artifacts/examples/claude-example-proof.json"
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
"message": "The Claude example flow must write the fixed proof artifact."
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|