@sweny-ai/core 0.1.62 → 0.1.64
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/dist/__tests__/cascade.test.d.ts +11 -0
- package/dist/__tests__/cascade.test.js +449 -0
- package/dist/cli/main.js +4 -4
- package/dist/cli/marketplace.d.ts +103 -0
- package/dist/cli/marketplace.js +290 -0
- package/dist/cli/marketplace.test.d.ts +1 -0
- package/dist/cli/marketplace.test.js +355 -0
- package/dist/cli/new.d.ts +35 -2
- package/dist/cli/new.js +156 -51
- package/dist/cli/new.test.js +215 -2
- package/dist/executor.js +105 -26
- package/dist/schema.d.ts +724 -0
- package/dist/schema.js +51 -1
- package/dist/source-resolver.d.ts +22 -0
- package/dist/source-resolver.js +174 -0
- package/dist/sources.d.ts +9 -6
- package/dist/sources.js +13 -146
- package/dist/sources.test.js +2 -1
- package/dist/sources.validation.test.js +2 -1
- package/dist/templates.js +2 -1
- package/dist/types.d.ts +20 -0
- package/package.json +1 -1
package/dist/executor.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* This replaces ~8k lines of engine + recipe step code.
|
|
14
14
|
*/
|
|
15
15
|
import { consoleLogger } from "./types.js";
|
|
16
|
-
import { resolveSources } from "./
|
|
16
|
+
import { resolveSources } from "./source-resolver.js";
|
|
17
17
|
/**
|
|
18
18
|
* Execute a workflow from entry to completion.
|
|
19
19
|
*
|
|
@@ -35,12 +35,29 @@ export async function execute(workflow, input, options) {
|
|
|
35
35
|
const trace = { steps: [], edges: [], sources: {} };
|
|
36
36
|
validate(workflow, skills);
|
|
37
37
|
// ── Source resolution phase ─────────────────────────────────────
|
|
38
|
-
// Resolve all
|
|
39
|
-
// before the main execution loop. This eagerly
|
|
38
|
+
// Resolve all Source values (node instructions + rules/context at every
|
|
39
|
+
// level) into plain text before the main execution loop. This eagerly
|
|
40
|
+
// fetches file/URL content so the execution path is pure-compute.
|
|
40
41
|
const sourceMap = {};
|
|
42
|
+
// Node instructions
|
|
41
43
|
for (const [nodeId, node] of Object.entries(workflow.nodes)) {
|
|
42
44
|
sourceMap[`nodes.${nodeId}.instruction`] = node.instruction;
|
|
43
45
|
}
|
|
46
|
+
// Runtime input rules/context (accepts Source | Source[])
|
|
47
|
+
const inputRules = extractRuntimeInputSources(input, "rules");
|
|
48
|
+
const inputContext = extractRuntimeInputSources(input, "context");
|
|
49
|
+
inputRules.forEach((s, i) => (sourceMap[`input.rules.${i}`] = s));
|
|
50
|
+
inputContext.forEach((s, i) => (sourceMap[`input.context.${i}`] = s));
|
|
51
|
+
// Workflow-level rules/context
|
|
52
|
+
(workflow.rules ?? []).forEach((s, i) => (sourceMap[`workflow.rules.${i}`] = s));
|
|
53
|
+
(workflow.context ?? []).forEach((s, i) => (sourceMap[`workflow.context.${i}`] = s));
|
|
54
|
+
// Node-level rules/context
|
|
55
|
+
for (const [nodeId, node] of Object.entries(workflow.nodes)) {
|
|
56
|
+
const nodeRules = nodeSourcesToArray(node.rules).sources;
|
|
57
|
+
const nodeContext = nodeSourcesToArray(node.context).sources;
|
|
58
|
+
nodeRules.forEach((s, i) => (sourceMap[`nodes.${nodeId}.rules.${i}`] = s));
|
|
59
|
+
nodeContext.forEach((s, i) => (sourceMap[`nodes.${nodeId}.context.${i}`] = s));
|
|
60
|
+
}
|
|
44
61
|
const resolvedSources = await resolveSources(sourceMap, {
|
|
45
62
|
cwd: options.cwd ?? process.cwd(),
|
|
46
63
|
env: options.env ?? process.env,
|
|
@@ -89,8 +106,10 @@ export async function execute(workflow, input, options) {
|
|
|
89
106
|
return output;
|
|
90
107
|
},
|
|
91
108
|
}));
|
|
92
|
-
// Prepend rules and context to instruction
|
|
93
|
-
const
|
|
109
|
+
// Prepend rules and context to instruction per cascade semantics
|
|
110
|
+
const effectiveRules = assembleCascaded("rules", currentId, workflow.nodes[currentId], inputRules.length, (workflow.rules ?? []).length, resolvedSources);
|
|
111
|
+
const effectiveContext = assembleCascaded("context", currentId, workflow.nodes[currentId], inputContext.length, (workflow.context ?? []).length, resolvedSources);
|
|
112
|
+
const instruction = buildNodeInstruction(resolvedInstruction, effectiveRules, effectiveContext, input, skillInstructions);
|
|
94
113
|
// Run Claude on this node
|
|
95
114
|
const result = await claude.run({
|
|
96
115
|
instruction,
|
|
@@ -137,31 +156,33 @@ export async function execute(workflow, input, options) {
|
|
|
137
156
|
}
|
|
138
157
|
// ─── Internals ───────────────────────────────────────────────────
|
|
139
158
|
/**
|
|
140
|
-
* Build the full instruction for a node
|
|
141
|
-
*
|
|
142
|
-
*
|
|
159
|
+
* Build the full instruction for a node.
|
|
160
|
+
*
|
|
161
|
+
* Assembly order (each section separated by `---`):
|
|
162
|
+
* 1. Rules (from runtime input + workflow + node, cascaded)
|
|
163
|
+
* 2. Context (from runtime input + workflow + node, cascaded)
|
|
164
|
+
* 3. Skill instructions (one per skill with an `instruction` field)
|
|
165
|
+
* 4. The node's base instruction
|
|
166
|
+
*
|
|
167
|
+
* Preserves the legacy `input.additionalContext` fallback for callers
|
|
168
|
+
* that predate the Source-based rules/context fields.
|
|
143
169
|
*/
|
|
144
|
-
function buildNodeInstruction(baseInstruction, input, skillInstructions) {
|
|
145
|
-
const inp = input;
|
|
170
|
+
function buildNodeInstruction(baseInstruction, effectiveRules, effectiveContext, input, skillInstructions) {
|
|
146
171
|
const sections = [];
|
|
147
|
-
if (
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if (
|
|
158
|
-
|
|
159
|
-
if (legacy) {
|
|
160
|
-
sections.push(`## Additional Context & Rules\n\n${legacy}`);
|
|
161
|
-
}
|
|
172
|
+
if (effectiveRules) {
|
|
173
|
+
sections.push(`## Rules — You MUST Follow These\n\n${effectiveRules}`);
|
|
174
|
+
}
|
|
175
|
+
if (effectiveContext) {
|
|
176
|
+
sections.push(`## Background Context\n\n${effectiveContext}`);
|
|
177
|
+
}
|
|
178
|
+
// Legacy fallback for `input.additionalContext` when no rules/context cascaded
|
|
179
|
+
if (sections.length === 0) {
|
|
180
|
+
const inp = input;
|
|
181
|
+
const legacy = inp && typeof inp.additionalContext === "string" ? inp.additionalContext : "";
|
|
182
|
+
if (legacy) {
|
|
183
|
+
sections.push(`## Additional Context & Rules\n\n${legacy}`);
|
|
162
184
|
}
|
|
163
185
|
}
|
|
164
|
-
// Skill instructions — injected between context and base instruction
|
|
165
186
|
if (skillInstructions && skillInstructions.length > 0) {
|
|
166
187
|
for (const { name, instruction } of skillInstructions) {
|
|
167
188
|
sections.push(`## Skill: ${name}\n\n${instruction}`);
|
|
@@ -171,6 +192,64 @@ function buildNodeInstruction(baseInstruction, input, skillInstructions) {
|
|
|
171
192
|
return baseInstruction;
|
|
172
193
|
return `${sections.join("\n\n---\n\n")}\n\n---\n\n${baseInstruction}`;
|
|
173
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* Normalize a NodeSources value into { sources, only }.
|
|
197
|
+
* - Array form → additive (only = false)
|
|
198
|
+
* - Object form → use its `only` and `sources`
|
|
199
|
+
* - undefined → empty additive
|
|
200
|
+
*/
|
|
201
|
+
function nodeSourcesToArray(ns) {
|
|
202
|
+
if (!ns)
|
|
203
|
+
return { sources: [], only: false };
|
|
204
|
+
if (Array.isArray(ns))
|
|
205
|
+
return { sources: ns, only: false };
|
|
206
|
+
return { sources: ns.sources, only: !!ns.only };
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Extract rules/context Sources from runtime input.
|
|
210
|
+
*
|
|
211
|
+
* Runtime input is typed `unknown` because workflows are polymorphic.
|
|
212
|
+
* We accept `Source`, `Source[]`, or undefined. A single string is
|
|
213
|
+
* classified via the usual Source prefix rules (inline by default).
|
|
214
|
+
* Invalid shapes are ignored — input is caller-controlled and the
|
|
215
|
+
* rest of the executor validates it elsewhere.
|
|
216
|
+
*/
|
|
217
|
+
function extractRuntimeInputSources(input, field) {
|
|
218
|
+
if (!input || typeof input !== "object")
|
|
219
|
+
return [];
|
|
220
|
+
const v = input[field];
|
|
221
|
+
if (v == null)
|
|
222
|
+
return [];
|
|
223
|
+
if (Array.isArray(v))
|
|
224
|
+
return v;
|
|
225
|
+
if (typeof v === "string")
|
|
226
|
+
return [v];
|
|
227
|
+
if (typeof v === "object")
|
|
228
|
+
return [v]; // tagged form {inline}/{file}/{url}
|
|
229
|
+
return [];
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Assemble the effective rules or context for a node per cascade semantics:
|
|
233
|
+
* runtime input + workflow-level + node-level
|
|
234
|
+
* unless the node sets `only: true` for that field, which discards input
|
|
235
|
+
* and workflow contributions and uses only the node's own sources.
|
|
236
|
+
*
|
|
237
|
+
* Returns the concatenated resolved content (empty string when nothing applies).
|
|
238
|
+
*/
|
|
239
|
+
function assembleCascaded(field, nodeId, node, inputCount, workflowCount, resolved) {
|
|
240
|
+
const { sources: nodeSources, only } = nodeSourcesToArray(node[field]);
|
|
241
|
+
const parts = [];
|
|
242
|
+
if (!only) {
|
|
243
|
+
for (let i = 0; i < inputCount; i++)
|
|
244
|
+
parts.push(resolved[`input.${field}.${i}`]?.content ?? "");
|
|
245
|
+
for (let i = 0; i < workflowCount; i++)
|
|
246
|
+
parts.push(resolved[`workflow.${field}.${i}`]?.content ?? "");
|
|
247
|
+
}
|
|
248
|
+
for (let i = 0; i < nodeSources.length; i++) {
|
|
249
|
+
parts.push(resolved[`nodes.${nodeId}.${field}.${i}`]?.content ?? "");
|
|
250
|
+
}
|
|
251
|
+
return parts.filter((s) => s.length > 0).join("\n\n");
|
|
252
|
+
}
|
|
174
253
|
/**
|
|
175
254
|
* Call observer without letting exceptions crash the workflow.
|
|
176
255
|
*
|