jev-devtools 0.2.0 → 0.3.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.
Files changed (39) hide show
  1. package/README.md +47 -3
  2. package/lib/flow/index.d.ts +82 -0
  3. package/lib/flow/index.js +588 -0
  4. package/lib/server/flows.d.ts +25 -0
  5. package/lib/server/flows.js +115 -0
  6. package/lib/server/index.d.ts +8 -0
  7. package/lib/server/index.js +85 -1
  8. package/lib/server/recorder.d.ts +4 -1
  9. package/lib/server/recorder.js +12 -1
  10. package/lib/src/ChoiceProbabilities.js +1 -1
  11. package/lib/src/ConnectedInspector.js +13 -4
  12. package/lib/src/DiagramContent.d.ts +6 -0
  13. package/lib/src/DiagramContent.js +32 -0
  14. package/lib/src/FlowOverview.d.ts +7 -0
  15. package/lib/src/FlowOverview.js +228 -0
  16. package/lib/src/JevDevtools.d.ts +6 -1
  17. package/lib/src/JevDevtools.js +18 -4
  18. package/lib/src/TraceDiagram.d.ts +4 -1
  19. package/lib/src/TraceDiagram.js +30 -12
  20. package/lib/src/condition-inputs.d.ts +9 -0
  21. package/lib/src/condition-inputs.js +100 -0
  22. package/lib/src/flow-condition.d.ts +4 -0
  23. package/lib/src/flow-condition.js +47 -0
  24. package/lib/src/flow-model.d.ts +7 -2
  25. package/lib/src/flow-model.js +2 -11
  26. package/lib/src/flow-protocol.d.ts +68 -0
  27. package/lib/src/flow-protocol.js +1 -0
  28. package/lib/src/flow-stream.d.ts +8 -0
  29. package/lib/src/flow-stream.js +80 -0
  30. package/lib/src/inspector-styles.js +1 -1
  31. package/lib/src/inspector.css +501 -8
  32. package/lib/src/protocol.d.ts +5 -0
  33. package/lib/src/useDiagramViewport.d.ts +4 -0
  34. package/lib/src/useDiagramViewport.js +20 -3
  35. package/package.json +6 -2
  36. package/skills/setup-jev-devtools/SKILL.md +6 -2
  37. package/skills/setup-jev-devtools/references/flows.md +150 -0
  38. package/skills/setup-jev-devtools/references/nextjs.md +1 -1
  39. package/skills/setup-jev-devtools/references/verify.md +1 -1
@@ -0,0 +1,150 @@
1
+ # Build and inspect a Jev flow
2
+
3
+ For `jev-devtools` 0.3.0 and later. Read the installed package version and `lib/flow/index.d.ts` first; online `main` may describe newer APIs. Use this recipe when the user asks to connect multiple Jev requests, inspect branches, or visualize parallel checks. Adding the inspector alone does not require converting existing calls to flows.
4
+
5
+ ## Plan the integration
6
+
7
+ 1. Complete the existing [React/Node](react-node.md) or [Next.js](nextjs.md) setup. Reuse its shared inspector and instrumented client; do not create a second recorder for flows.
8
+ 2. Identify the input shape, questions, execution order, decision thresholds, parallel dependencies, and named outcomes in the user's application. Preserve existing policies. If a threshold or action is unspecified, ask rather than inventing it.
9
+ 3. Import `defineJevFlow` (and `all`, `any`, `not` when needed) from **server-only** `jev-devtools/flow`. Define the graph once at module scope and call `run()` from the existing Node handler/action. In Next.js, mark the module `server-only` and keep the executing route/segment on the Node runtime. No new client provider or root component is needed.
10
+ 4. Keep business actions such as refunds, notifications, and database writes in the application after the returned outcome. A named output does not execute an action. Preserve authorization, idempotency, input validation, and error handling around those actions.
11
+
12
+ If importing question helpers as below, install `@typesafe-ai/sdk` as a direct dependency in the server workspace using its package manager. The 0.3.0 release is verified with SDK 0.7.0; preserve a compatible existing version rather than silently changing SDK semantics. The flow also accepts ordinary SDK question objects.
13
+
14
+ ## Complete example: parallel checks, one decision, two outcomes
15
+
16
+ The 90% threshold here is an illustrative application policy, not a Jev default. The example's `inspector` and `jev` come from the shared server setup above.
17
+
18
+ ```ts
19
+ import { noul } from '@typesafe-ai/sdk';
20
+ import { defineJevFlow } from 'jev-devtools/flow';
21
+
22
+ type Ticket = { message: string };
23
+
24
+ export const reviewFlow = defineJevFlow<Ticket>('ticket-review', (flow) => {
25
+ const checks = flow.parallel('checks', {
26
+ eligibility: (lane) => lane.jev('eligibility', {
27
+ state: flow.input,
28
+ questions: { eligible: noul('Is this ticket eligible for review?') },
29
+ }, {
30
+ title: 'Check eligibility',
31
+ description: 'Evaluate whether this ticket can enter the review process.',
32
+ }).answer('eligible').noul,
33
+ risk: (lane) => lane.jev('risk', {
34
+ state: flow.input,
35
+ questions: { risky: noul('Does this ticket need extra risk review?') },
36
+ }, {
37
+ title: 'Check risk',
38
+ description: 'Evaluate risk independently of eligibility.',
39
+ }).answer('risky').noul,
40
+ }, {
41
+ title: 'Wait for both checks',
42
+ description: 'Continue after eligibility and risk have both succeeded.',
43
+ });
44
+
45
+ const decision = flow.jev('decision', {
46
+ state: { ticket: flow.input, eligibility: checks.eligibility, risk: checks.risk },
47
+ questions: { ready: noul('Is this ticket ready for approval?') },
48
+ }, {
49
+ title: 'Assess readiness',
50
+ description: 'Ask Jev to assess the ticket using both completed checks.',
51
+ });
52
+
53
+ return flow.branch('readiness', {
54
+ when: decision.answer('ready').noul.gte(0.9),
55
+ then: (path) => path.output('ready', {
56
+ readiness: decision.answer('ready').noul,
57
+ }, {
58
+ title: 'Ready for approval',
59
+ description: 'Return readiness to the application. No approval is executed here.',
60
+ }),
61
+ else: (path) => path.output('manual-review', undefined, {
62
+ title: 'Needs manual review',
63
+ description: 'Readiness is below the application threshold.',
64
+ }),
65
+ }, {
66
+ title: 'Require at least 90% readiness',
67
+ description: 'Use the returned readiness probability to choose a route.',
68
+ });
69
+ });
70
+ ```
71
+
72
+ Invoke from your existing server handler after validating the input:
73
+
74
+ ```ts
75
+ const result = await reviewFlow.run({ message: 'I was charged twice.' }, {
76
+ client: jev, // Created by this same inspector.
77
+ devtools: inspector,
78
+ signal: request.signal, // Optional AbortSignal when the framework provides one.
79
+ });
80
+ // { runId, outcome: 'ready', value: { readiness: 0.94 } }
81
+ // Or { runId, outcome: 'manual-review', value: undefined }.
82
+ ```
83
+
84
+ For custom server-derived sessions, use `await inspector.run(request, () => reviewFlow.run(input, { client: jev, devtools: inspector, signal: request.signal }))`. This ensures the run and its calls use the same session as the authorized inspector connection. Do not accept an arbitrary client-supplied session ID.
85
+
86
+ ## API rules that affect generated code
87
+
88
+ | API | Contract |
89
+ | --- | --- |
90
+ | `defineJevFlow<Input>(name, build)` | Synchronous definition; no requests run while building. Return `flow.output(...)` or `flow.branch(...)`. Each `run` is independent. |
91
+ | `flow.jev(id, request, display?)` | One SDK `systemOne` call when reached. Sequential within its block. Use normal SDK questions/options/assets; references resolve in state. |
92
+ | `flow.input.get('field')` | A typed expression referencing the run input, not a value available during definition. |
93
+ | `result.answer('name')` | Typed answer fields: `.choice`, `.probabilities.get('label')`, `.confidence`, `.noul`, `.score`, `.legend`, or `.box` as supported by that question's SDK result. Select nested fields with `.get(key)`. |
94
+ | `value.eq(other)`, `.lt(number)`, `.gte(number)` | Matching scalar equality or finite numeric comparisons. Noul values are in 0–1; use `0.9`, not `90`. There is no `.gt()` or `.lte()` method. |
95
+ | `all(...)`, `any(...)`, `not(...)` | Compose Boolean expressions. Every operand evaluates; missing values are errors even in an otherwise short-circuited expression. Do not use JavaScript `if`, `&&`, or `>=` on expression objects. |
96
+ | `flow.branch(id, { when, then, else }, display?)` | Terminal in that block. Both callbacks define paths; only one executes. Each callback must return its own builder's `output` or nested `branch`. No common continuation after a branch in v1. |
97
+ | `flow.parallel(id, lanes, display?)` | Named lanes run concurrently. Each lane returns an expression. Use the resulting named references after all lanes succeed. Lane-local references cannot escape directly or cross into a sibling lane. |
98
+ | `flow.output(name, value?, display?)` | Terminal named result, optionally containing nested resolved expressions. Output names are globally unique node IDs. For metadata without a value, pass `undefined` second. |
99
+ | `definition.run(input, { client, devtools?, signal? })` | Returns `{ runId, outcome: string, value?: unknown }`. Narrow/validate values in application code; outcomes are not inferred as a discriminated union. A normal SDK client works without recording. |
100
+
101
+ Use the callback's current builder (`lane` or `path`) to add steps; do not call the outer builder inside it. References must belong to the same definition and be available on the current path. Reusing an earlier parent reference is allowed. IDs must be unique across all paths/lanes: 1–80 letters, digits, `_`, or `-`, starting with a letter or digit. Maximum 256 nodes and 16 nested blocks; no loops or arbitrary execution callbacks. Plain templates are copied when defining; treat SDK assets as immutable. Define once to avoid a new definition identity on every request; hot reload can still create a new identity.
102
+
103
+ SDK retries remain within each request; the flow adds no retries. A failed step blocks dependents and rejects the run. A failed lane aborts siblings and waits for them to settle. External cancellation propagates to SDK signals; already completed calls stay completed. Handle rejection in the app; do not retry a whole flow automatically or imply durable/resumable execution.
104
+
105
+ ## Diagram context and privacy
106
+
107
+ Every builder method accepts a third `{ title, description }` argument (`FlowDisplay`). Title: 1–120 characters; description: 1–600; supplied text must be nonblank. Titles change display text, not IDs, outcomes, questions, or execution. These are static developer-authored explanations, not model reasoning.
108
+
109
+ Titles/descriptions, flow names, step IDs, lane names and outcome names are structural metadata and are **not request-redacted**. Keep them static and public-safe. Request state/answers and output values use capture limits/redaction. Rule literals also use the configured redaction callback with paths beginning `['conditions', stepId]` and ending in `'value'`; preserve the app's redaction policy. Redaction never changes execution.
110
+
111
+ The condition card separates incoming captured answers from requirements, e.g. **Ready: 94% → At least 90%**. Its result comes from the actual recorded decision, not a fresh evaluation of redacted data. Evicted/missing answers and flow-input/parallel aggregate references show **Not captured**. Do not promise that every runtime value is retained. Full technical rules appear in selected-step details.
112
+
113
+ History is bounded in process (default 50 runs, maximum 200, independently from request history). Capture and display default off; disabled recording does not disable flow execution. Keep flows server-side in production too if application behavior depends on them. This is not a persistent or distributed workflow engine, and large flow canvases are not virtualized.
114
+
115
+ ## Verify without inference
116
+
117
+ Use a **separate, visibly labeled sample client** from the same inspector. Do not replace the user's live client or routes. With the `reviewFlow` above, this fixture exercises the actual SDK and flow executor:
118
+
119
+ ```ts
120
+ const sampleJev = inspector.createClient({
121
+ apiKey: 'sample-only',
122
+ logLevel: 'off',
123
+ fetch: async (_url, init) => {
124
+ const body = JSON.parse(String(init?.body));
125
+ const answers = Object.fromEntries(Object.keys(body.questions).map((name) => [
126
+ name, { type: 'noul', noul: name === 'risky' ? 0.1 : 0.94 },
127
+ ]));
128
+ return Response.json({ model: 'jev-sample', answers });
129
+ },
130
+ }, { label: 'Flow setup verification', source: 'sample' });
131
+
132
+ const result = await reviewFlow.run({ message: 'Sample ticket' }, {
133
+ client: sampleJev,
134
+ devtools: inspector,
135
+ });
136
+ // Expect outcome 'ready', value.readiness 0.94, and three captured calls.
137
+ ```
138
+
139
+ Run this inside the application process serving the inspector, using a temporary development-only handler or the app's existing harness. A separate CLI has separate history. Mount the same root drawer; the existing base route must forward `/flows/events` as well as `/events` and `/clear`. The Next optional catch-all already does this.
140
+
141
+ Check these observable results:
142
+
143
+ - The Flows view shows two parallel requests, their join, one downstream readiness request, a condition and two possible outcomes. Three real SDK calls use the local fixture; no inference network call occurs.
144
+ - The join waits for both lanes. The chosen output is `ready`; `manual-review` is marked untaken. Incoming readiness is 94%, the requirement is at least 90%, and each request opens its captured details.
145
+ - Change only the fixture's `ready` probability to `0.4`: the other outcome executes. Missing values must not silently become zero. Use the app's error/cancellation harness if it has one.
146
+ - App typecheck/build passes, `/flow` and `/server` stay out of client bundles, and production disables inspector endpoints/display while the application's flow still executes. Do not call live inference just to verify this.
147
+
148
+ If the flow executes but details are absent, check: `devtools` passed to `run`, `client` from that same adapter, same process/session, capture enabled, `/flows/events` forwarded, and histories not cleared/evicted. A plain SDK client can produce flow states but cannot link request details. See [verify.md](verify.md) for general endpoint checks.
149
+
150
+ Remove temporary verification routes unless requested as a demo. Report the installed version, integration location, outcomes observed, whether browser checks were performed, and that responses were samples. Never equate sample verification with live-model correctness.
@@ -49,7 +49,7 @@ export const GET = (request: Request) => inspector.handleNodeRoute(request);
49
49
  export const DELETE = (request: Request) => inspector.handleNodeRoute(request);
50
50
  ```
51
51
 
52
- Use the **optional** catch-all `[[...path]]`, which covers config at the base path as well as `/events` and `/clear`. Keep streams out of caches/proxy buffering. Match Host to the configured origin; `localhost` and `127.0.0.1` differ. For Next `basePath` or a custom endpoint, align server `endpoint`, client `endpoint`, and the externally visible route path.
52
+ Use the **optional** catch-all `[[...path]]`, which covers config at the base path as well as `/events`, `/flows/events`, and `/clear`. Keep streams out of caches/proxy buffering. Match Host to the configured origin; `localhost` and `127.0.0.1` differ. For Next `basePath` or a custom endpoint, align server `endpoint`, client `endpoint`, and the externally visible route path.
53
53
 
54
54
  ## Root client boundary and CSS
55
55
 
@@ -78,6 +78,6 @@ Report HTTP/capture checks separately from visual browser checks, and sample ver
78
78
  | Server import/build errors | Keep `/server` outside client modules. In Next externalize the SDK only. Use a supported Node runtime. |
79
79
  | Production 404 | Expected when disabled; app inference remains independent. |
80
80
 
81
- Capture is bounded to 50 calls in memory by default. Built-in redaction has limits; arbitrary secrets in prose are not detected. Diagrams show captured inputs/outputs, not hidden model reasoning or application routing decisions.
81
+ Capture is bounded to 50 calls in memory by default. Built-in redaction has limits; arbitrary secrets in prose are not detected. Diagrams show captured inputs/outputs, not hidden model reasoning. Routing decisions are recorded only for explicitly defined flows; see [flows.md](flows.md) for flow verification.
82
82
 
83
83
  The current browser requests `/events?protocol=2`: the first event is a full version 2 `snapshot`, followed by `delta` events containing changed traces and the retained ID order. The unqualified `/events` check above intentionally remains compatible with legacy clients. Reconnecting always supplies a fresh snapshot.