@xdelivered/emberflow 0.2.0 → 0.5.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/bin/commands.ts +7 -3
- package/bin/init.test.ts +94 -0
- package/bin/init.ts +108 -2
- package/dist/bin/commands.d.ts +1 -0
- package/dist/bin/commands.js +6 -3
- package/dist/bin/init.d.ts +4 -0
- package/dist/bin/init.js +96 -2
- package/dist/server/agents/codexParse.js +31 -0
- package/dist/server/agents/detect.d.ts +18 -8
- package/dist/server/agents/detect.js +78 -13
- package/dist/server/agents/modelRejectionHint.js +1 -1
- package/dist/server/agents/prompt.d.ts +15 -1
- package/dist/server/agents/prompt.js +59 -37
- package/dist/server/agents/runManager.d.ts +23 -2
- package/dist/server/agents/runManager.js +36 -8
- package/dist/server/client.d.ts +1 -0
- package/dist/server/client.js +7 -2
- package/dist/server/index.js +208 -16
- package/dist/server/nodesPayload.d.ts +14 -1
- package/dist/server/nodesPayload.js +15 -2
- package/dist/server/projectMode.js +5 -2
- package/dist/server/runRegistry.d.ts +52 -1
- package/dist/server/runRegistry.js +170 -1
- package/dist/server/sourceNav.d.ts +77 -0
- package/dist/server/sourceNav.js +503 -0
- package/dist/server/subflowRunner.d.ts +48 -1
- package/dist/server/subflowRunner.js +34 -7
- package/dist/server/updateCheck.d.ts +68 -0
- package/dist/server/updateCheck.js +142 -0
- package/dist/src/engine/executor.js +4 -3
- package/dist/src/engine/registry.d.ts +27 -2
- package/dist/src/engine/registry.js +84 -2
- package/dist/src/nodes/index.d.ts +8 -2
- package/dist/src/nodes/index.js +7 -3
- package/dist/src/nodes/login.d.ts +3 -1
- package/dist/src/nodes/login.js +2 -2
- package/package.json +28 -7
- package/server/agentRoute.test.ts +20 -0
- package/server/agents/codexParse.test.ts +37 -0
- package/server/agents/codexParse.ts +34 -0
- package/server/agents/detect.test.ts +26 -7
- package/server/agents/detect.ts +82 -14
- package/server/agents/modelRejectionHint.ts +2 -1
- package/server/agents/prompt.test.ts +128 -0
- package/server/agents/prompt.ts +102 -3
- package/server/agents/runManager.test.ts +9 -0
- package/server/agents/runManager.ts +37 -7
- package/server/client.ts +10 -4
- package/server/index.ts +213 -15
- package/server/nodeRun.test.ts +189 -0
- package/server/nodesPayload.test.ts +38 -0
- package/server/nodesPayload.ts +28 -3
- package/server/projectMode.ts +5 -2
- package/server/runRegistry.ts +200 -3
- package/server/setupStatus.test.ts +3 -0
- package/server/sourceNav.test.ts +382 -0
- package/server/sourceNav.ts +644 -0
- package/server/sourceNavRoute.test.ts +163 -0
- package/server/stepDrill.test.ts +380 -0
- package/server/subflowRunner.ts +92 -11
- package/server/updateCheck.test.ts +209 -0
- package/server/updateCheck.ts +175 -0
- package/server/workflowTestRoute.test.ts +1 -1
- package/src/App.tsx +82 -2
- package/src/components/AgentConsole.tsx +7 -280
- package/src/components/AgentStream.test.tsx +88 -0
- package/src/components/AgentStream.tsx +303 -0
- package/src/components/CreateModal.tsx +43 -9
- package/src/components/Dock.tsx +1 -1
- package/src/components/EmptyState.test.tsx +49 -0
- package/src/components/EmptyState.tsx +61 -0
- package/src/components/EnvironmentDialog.tsx +4 -1
- package/src/components/EnvironmentPicker.tsx +8 -3
- package/src/components/EnvironmentsDialog.tsx +4 -1
- package/src/components/InfraPanel.tsx +30 -2
- package/src/components/InfrastructureDialog.test.tsx +97 -0
- package/src/components/InfrastructureDialog.tsx +111 -0
- package/src/components/Inspector.tsx +9 -26
- package/src/components/RunbookView.tsx +70 -6
- package/src/components/SettingsDialog.tsx +4 -59
- package/src/components/Sidebar.tsx +6 -26
- package/src/components/SourceNavigator.test.tsx +226 -0
- package/src/components/SourceNavigator.tsx +520 -0
- package/src/components/StatusBar.tsx +227 -26
- package/src/components/Toolbar.tsx +28 -16
- package/src/components/UpdateChip.test.tsx +31 -0
- package/src/components/WelcomeDialog.test.tsx +384 -13
- package/src/components/WelcomeDialog.tsx +996 -177
- package/src/engine/executor.ts +4 -3
- package/src/engine/registry.sourceRef.test.ts +112 -0
- package/src/engine/registry.ts +103 -2
- package/src/lib/guidedQuestions.test.ts +224 -0
- package/src/lib/guidedQuestions.ts +181 -0
- package/src/lib/runbookModel.ts +3 -3
- package/src/lib/runbookProjection.test.ts +43 -0
- package/src/lib/runbookProjection.ts +26 -6
- package/src/nodes/index.ts +10 -3
- package/src/nodes/login.ts +5 -2
- package/src/store/agentClient.ts +27 -8
- package/src/store/apiTree.ts +12 -0
- package/src/store/builderStore.test.ts +441 -99
- package/src/store/builderStore.ts +385 -314
- package/src/store/nodeMeta.ts +10 -2
- package/src/store/serverRunner.ts +56 -5
- package/src/store/setupClient.ts +7 -2
- package/src/store/sourceNavClient.ts +48 -0
- package/src/store/updateClient.ts +50 -0
- package/studio-dist/assets/index-BbzppDpt.js +65 -0
- package/studio-dist/assets/index-CLf6blcA.css +1 -0
- package/studio-dist/index.html +2 -2
- package/templates/skills/emberflow-basics/SKILL.md +29 -1
- package/templates/skills/emberflow-model-process/SKILL.md +92 -9
- package/templates/skills/emberflow-new-workflow/SKILL.md +8 -1
- package/templates/skills/emberflow-review-workflow/SKILL.md +64 -1
- package/studio-dist/assets/index-DNJwW-hM.css +0 -1
- package/studio-dist/assets/index-O26dKRjW.js +0 -65
|
@@ -57,6 +57,10 @@ export class RunRegistry {
|
|
|
57
57
|
// the child via loadFlow, runs it to completion on the same registry
|
|
58
58
|
// sharing the parent's environment, and forwards its (prefixed) logs into
|
|
59
59
|
// this run's SSE stream via `onLog`.
|
|
60
|
+
// Stepped runs share ONE drill state across the root's runner and every
|
|
61
|
+
// nested child's runner (makeSubflowRunner threads `opts` through), so a
|
|
62
|
+
// grandchild pushes onto the same stack and nesting works.
|
|
63
|
+
const drillState = opts.stepped ? { stack: [] } : undefined;
|
|
60
64
|
const subflowRunner = makeSubflowRunner({
|
|
61
65
|
loadFlow: (id) => this.loadFlow?.(id),
|
|
62
66
|
registry: this.registry,
|
|
@@ -67,6 +71,19 @@ export class RunRegistry {
|
|
|
67
71
|
mockRun: opts.mockRun,
|
|
68
72
|
trace: this.sink,
|
|
69
73
|
onLog: (line) => emit({ type: 'log', line }),
|
|
74
|
+
drill: drillState,
|
|
75
|
+
// Stepped children stream node states into this run's SSE stream,
|
|
76
|
+
// tagged with the CHILD flow's id so the client can route them.
|
|
77
|
+
// After a drill-aware cancel the rejected parent executions unwind in
|
|
78
|
+
// the background (recording their Subflow nodes as failed) — child
|
|
79
|
+
// states from that unwinding must not reach subscribers post-finish.
|
|
80
|
+
onNodeState: drillState
|
|
81
|
+
? (workflowId, nodeId, state) => {
|
|
82
|
+
if (drillState.cancelled)
|
|
83
|
+
return;
|
|
84
|
+
emit({ type: 'nodeState', workflowId, nodeId, state });
|
|
85
|
+
}
|
|
86
|
+
: undefined,
|
|
70
87
|
}, [flow.id]);
|
|
71
88
|
const handle = startRun({
|
|
72
89
|
flow,
|
|
@@ -82,9 +99,14 @@ export class RunRegistry {
|
|
|
82
99
|
trace: this.sink,
|
|
83
100
|
subflowRunner,
|
|
84
101
|
events: {
|
|
85
|
-
onNodeStateChange: (nodeId, state) => emit({ type: 'nodeState', nodeId, state }),
|
|
102
|
+
onNodeStateChange: (nodeId, state) => emit({ type: 'nodeState', workflowId: flow.id, nodeId, state }),
|
|
86
103
|
onLog: (line) => emit({ type: 'log', line }),
|
|
87
104
|
onRunFinished: (run) => {
|
|
105
|
+
// Idempotent: after a drill-aware cancel the root executor's pending
|
|
106
|
+
// Subflow execution can fail in the background and re-finish the run
|
|
107
|
+
// — subscribers already saw the cancelled `finished` event.
|
|
108
|
+
if (entry.finished)
|
|
109
|
+
return;
|
|
88
110
|
emit({
|
|
89
111
|
type: 'finished',
|
|
90
112
|
run,
|
|
@@ -100,9 +122,156 @@ export class RunRegistry {
|
|
|
100
122
|
},
|
|
101
123
|
});
|
|
102
124
|
entry.handle = handle;
|
|
125
|
+
if (drillState)
|
|
126
|
+
entry.drill = { state: drillState };
|
|
103
127
|
this.runs.set(handle.run.id, entry);
|
|
104
128
|
return { runId: handle.run.id, handle };
|
|
105
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* One composite step of a run, drill-aware. For stepped runs a Subflow node
|
|
132
|
+
* is no longer one opaque step: entering it pauses inside the child
|
|
133
|
+
* (`entered`), subsequent steps drive the child's nodes, and completing the
|
|
134
|
+
* child pops back out (`exited`). Non-stepped runs (no drill state) fall
|
|
135
|
+
* back to a plain executor step. Unknown run → undefined.
|
|
136
|
+
*/
|
|
137
|
+
async step(runId) {
|
|
138
|
+
const entry = this.runs.get(runId);
|
|
139
|
+
if (!entry)
|
|
140
|
+
return undefined;
|
|
141
|
+
// Serialize per run: concurrent step() calls would each arm
|
|
142
|
+
// `state.onChildStarted` (last writer wins) and double-drive the
|
|
143
|
+
// executor, so the second caller waits for the first to settle.
|
|
144
|
+
const result = (entry.stepChain ?? Promise.resolve()).then(() => this.stepLevels(entry));
|
|
145
|
+
entry.stepChain = result.catch(() => undefined);
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* One composite step, drill-aware. Structured as a loop over levels: each
|
|
150
|
+
* pass drives the current deepest level (a pending stashed step, or a fresh
|
|
151
|
+
* executor step) while racing "a new child started". Popping a finished
|
|
152
|
+
* child continues the loop on its parent's stashed step WITH the race still
|
|
153
|
+
* armed — the parent's still-pending Subflow node execution may call
|
|
154
|
+
* runSubflow again (retry, or a custom node making sequential calls), and
|
|
155
|
+
* that re-entry must surface as `entered`, not deadlock the fold.
|
|
156
|
+
*/
|
|
157
|
+
async stepLevels(entry) {
|
|
158
|
+
const drill = entry.drill;
|
|
159
|
+
if (!drill) {
|
|
160
|
+
const more = await entry.handle.step();
|
|
161
|
+
return { done: !more };
|
|
162
|
+
}
|
|
163
|
+
const { state } = drill;
|
|
164
|
+
// Cancelled mid-drill: the stack was already unwound by cancel(); report
|
|
165
|
+
// done without driving anything.
|
|
166
|
+
if (state.cancelled)
|
|
167
|
+
return { done: true };
|
|
168
|
+
// Set once this step pops a child; carried onto whatever this step
|
|
169
|
+
// ultimately reports (including an `entered` for a re-run child).
|
|
170
|
+
let exited = false;
|
|
171
|
+
const withExit = (r) => (exited ? { ...r, exited: true } : r);
|
|
172
|
+
for (;;) {
|
|
173
|
+
// The level being stepped: the deepest drilled-in child, or the root run.
|
|
174
|
+
const level = state.stack.length > 0 ? state.stack[state.stack.length - 1] : undefined;
|
|
175
|
+
const handle = level ? level.handle : entry.handle;
|
|
176
|
+
const pendingStep = level ? level.pendingStep : drill.rootPendingStep;
|
|
177
|
+
const setPending = (p) => {
|
|
178
|
+
if (level)
|
|
179
|
+
level.pendingStep = p;
|
|
180
|
+
else
|
|
181
|
+
drill.rootPendingStep = p;
|
|
182
|
+
};
|
|
183
|
+
// A Subflow node's execution blocks inside runSubflow until its child
|
|
184
|
+
// completes, so the step promise alone can never report "entered a
|
|
185
|
+
// child" — arm the child-started signal BEFORE awaiting and race them.
|
|
186
|
+
const childStarted = new Promise((res) => {
|
|
187
|
+
state.onChildStarted = res;
|
|
188
|
+
});
|
|
189
|
+
const stepPromise = pendingStep ?? handle.step();
|
|
190
|
+
setPending(undefined);
|
|
191
|
+
let raced;
|
|
192
|
+
try {
|
|
193
|
+
raced = await Promise.race([
|
|
194
|
+
stepPromise.then((more) => ({ kind: 'stepped', more })),
|
|
195
|
+
childStarted.then((child) => ({ kind: 'entered', child })),
|
|
196
|
+
]);
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
state.onChildStarted = undefined;
|
|
200
|
+
if (!level)
|
|
201
|
+
throw err;
|
|
202
|
+
// A child level's step threw outright (not a recorded node failure —
|
|
203
|
+
// the executor catches those). Reject the parent's blocked runSubflow,
|
|
204
|
+
// which surfaces it as the Subflow node's error, then fold the parent
|
|
205
|
+
// (next loop pass, race re-armed).
|
|
206
|
+
state.stack.pop();
|
|
207
|
+
level.reject(err);
|
|
208
|
+
exited = true;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
state.onChildStarted = undefined;
|
|
212
|
+
if (raced.kind === 'entered') {
|
|
213
|
+
// The level's step stays pending for the whole child run — stash it;
|
|
214
|
+
// the step that later pops this child folds its boolean back in.
|
|
215
|
+
setPending(stepPromise);
|
|
216
|
+
return withExit({
|
|
217
|
+
done: false,
|
|
218
|
+
entered: { workflowId: raced.child.workflowId, nodeId: raced.child.viaNodeId },
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
if (raced.more)
|
|
222
|
+
return withExit({ done: false });
|
|
223
|
+
// This level has no more nodes: its run is complete.
|
|
224
|
+
if (!level)
|
|
225
|
+
return withExit({ done: true });
|
|
226
|
+
// Pop the finished child and hand its completed run (succeeded OR failed)
|
|
227
|
+
// to the parent's blocked runSubflow — the success/failure semantics live
|
|
228
|
+
// in subflowRunner, byte-identical to the non-stepped path. The parent's
|
|
229
|
+
// stashed step is folded in by the next loop pass, with the child-started
|
|
230
|
+
// race re-armed so a retrying Subflow node re-entering its child yields
|
|
231
|
+
// `exited` + `entered` instead of hanging.
|
|
232
|
+
state.stack.pop();
|
|
233
|
+
level.resolve(level.handle.run);
|
|
234
|
+
exited = true;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Cancel a run, drill-aware. For a stepped run drilled into subflows this
|
|
239
|
+
* cancels every stacked child deepest-first and rejects the promise each
|
|
240
|
+
* parent's Subflow node execution is blocked on — those pending executions
|
|
241
|
+
* then unwind in the background exactly like an in-flight node after a
|
|
242
|
+
* plain root cancel today (recorded, but the run is already finished).
|
|
243
|
+
* step() after cancel reports { done: true } without executing anything.
|
|
244
|
+
* Returns false for an unknown run.
|
|
245
|
+
*/
|
|
246
|
+
/** Cancels every live (non-finished) run — the server's shutdown path, so
|
|
247
|
+
* in-flight executions (and their drilled subflow children) don't keep
|
|
248
|
+
* running headless after the process is asked to die. */
|
|
249
|
+
shutdown() {
|
|
250
|
+
for (const [id, run] of this.runs) {
|
|
251
|
+
if (!run.finished)
|
|
252
|
+
this.cancel(id);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
cancel(runId) {
|
|
256
|
+
const entry = this.runs.get(runId);
|
|
257
|
+
if (!entry)
|
|
258
|
+
return false;
|
|
259
|
+
const drill = entry.drill;
|
|
260
|
+
if (drill) {
|
|
261
|
+
const { state } = drill;
|
|
262
|
+
state.cancelled = true;
|
|
263
|
+
state.onChildStarted = undefined;
|
|
264
|
+
while (state.stack.length > 0) {
|
|
265
|
+
const child = state.stack.pop();
|
|
266
|
+
child.pendingStep = undefined;
|
|
267
|
+
child.handle.cancel();
|
|
268
|
+
child.reject(new Error('run cancelled'));
|
|
269
|
+
}
|
|
270
|
+
drill.rootPendingStep = undefined;
|
|
271
|
+
}
|
|
272
|
+
entry.handle.cancel();
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
106
275
|
/**
|
|
107
276
|
* Best-effort: fires `errorOperation` (resolved via the same `loadFlow`
|
|
108
277
|
* callback Subflow nodes use) as a new run when `failedRun` finished with
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type * as TS from 'typescript';
|
|
2
|
+
/**
|
|
3
|
+
* Source navigation for the studio's node-implementation view: serve a whole
|
|
4
|
+
* project file plus a flat symbol table (top-level declarations, imports with
|
|
5
|
+
* resolutions, re-exports) so the client can render identifiers as links and
|
|
6
|
+
* navigate file-by-file. One flat parse per file, mtime-cached; recursion
|
|
7
|
+
* happens client-side by fetching the next file — except re-export chains,
|
|
8
|
+
* which the server follows (bounded, cycle-safe) so an import through an
|
|
9
|
+
* index module resolves straight to the declaring file.
|
|
10
|
+
*
|
|
11
|
+
* TypeScript is loaded lazily on first use (`await import('typescript')`);
|
|
12
|
+
* when that fails the file is still served with `resolver: 'unavailable'`
|
|
13
|
+
* and empty symbols, and the studio shows unresolved-with-reason.
|
|
14
|
+
*/
|
|
15
|
+
export type Resolution = {
|
|
16
|
+
kind: 'project';
|
|
17
|
+
path: string;
|
|
18
|
+
line?: number;
|
|
19
|
+
} | {
|
|
20
|
+
kind: 'external';
|
|
21
|
+
package: string;
|
|
22
|
+
} | {
|
|
23
|
+
kind: 'builtin';
|
|
24
|
+
} | {
|
|
25
|
+
kind: 'unresolved';
|
|
26
|
+
reason: string;
|
|
27
|
+
};
|
|
28
|
+
export interface SourceDeclaration {
|
|
29
|
+
name: string;
|
|
30
|
+
kind: 'fn' | 'const' | 'class' | 'other';
|
|
31
|
+
line: number;
|
|
32
|
+
endLine: number;
|
|
33
|
+
exported: boolean;
|
|
34
|
+
}
|
|
35
|
+
export interface SourceImport {
|
|
36
|
+
/** Imported name in the source module ('default', '*' for namespace, 'import()' for dynamic). */
|
|
37
|
+
name: string;
|
|
38
|
+
/** Local binding name in the served file ('' when there is none). */
|
|
39
|
+
local: string;
|
|
40
|
+
/** The specifier as written. */
|
|
41
|
+
from: string;
|
|
42
|
+
resolution: Resolution;
|
|
43
|
+
}
|
|
44
|
+
export interface SourceReexport {
|
|
45
|
+
name: string;
|
|
46
|
+
from: string;
|
|
47
|
+
resolution: Resolution;
|
|
48
|
+
}
|
|
49
|
+
export interface SourceFilePayload {
|
|
50
|
+
path: string;
|
|
51
|
+
content: string;
|
|
52
|
+
language: 'ts' | 'js';
|
|
53
|
+
/** Present (as 'unavailable') only when the typescript module could not be loaded. */
|
|
54
|
+
resolver?: 'unavailable';
|
|
55
|
+
symbols: {
|
|
56
|
+
declarations: SourceDeclaration[];
|
|
57
|
+
imports: SourceImport[];
|
|
58
|
+
reexports: SourceReexport[];
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export type SourceFileResult = {
|
|
62
|
+
ok: true;
|
|
63
|
+
payload: SourceFilePayload;
|
|
64
|
+
} | {
|
|
65
|
+
ok: false;
|
|
66
|
+
status: 400 | 404;
|
|
67
|
+
error: string;
|
|
68
|
+
};
|
|
69
|
+
export interface SourceNavOptions {
|
|
70
|
+
/** Test seam: overrides the lazy `import('typescript')` for this call only. */
|
|
71
|
+
tsLoader?: () => Promise<TsModule>;
|
|
72
|
+
}
|
|
73
|
+
type TsModule = typeof TS;
|
|
74
|
+
/** Clears all sourceNav caches (parse, tsconfig, ts module) — tests only. */
|
|
75
|
+
export declare function resetSourceNavCaches(): void;
|
|
76
|
+
export declare function getSourceFile(projectRoot: string, relPath: string, opts?: SourceNavOptions): Promise<SourceFileResult>;
|
|
77
|
+
export {};
|