howone 0.2.3 → 0.2.5
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/package.json +1 -1
- package/templates/vite/.howone/skills/howone/01-architect/01-app-generation.md +8 -7
- package/templates/vite/.howone/skills/howone/01-architect/02-manifest-codegen.md +121 -436
- package/templates/vite/.howone/skills/howone/03-ai-capabilities/04-workflow-operations.md +13 -4
- package/templates/vite/.howone/skills/howone/04-app-sdk/01-client-setup.md +94 -261
- package/templates/vite/.howone/skills/howone/04-app-sdk/02-entity-operations.md +85 -465
- package/templates/vite/.howone/skills/howone/04-app-sdk/03-auth.md +11 -7
- package/templates/vite/.howone/skills/howone/04-app-sdk/04-react-integration.md +84 -137
- package/templates/vite/.howone/skills/howone/04-app-sdk/05-file-upload.md +66 -273
- package/templates/vite/.howone/skills/howone/04-app-sdk/06-raw-http.md +72 -249
- package/templates/vite/.howone/skills/howone/04-app-sdk/07-ai-action-calls.md +135 -499
- package/templates/vite/.howone/skills/howone/04-app-sdk/08-ai-manifest-handoff.md +49 -196
- package/templates/vite/.howone/skills/howone/04-app-sdk/09-extension-boundaries.md +4 -4
- package/templates/vite/.howone/skills/howone/04-app-sdk/10-workflow-execute-sse.md +94 -61
- package/templates/vite/.howone/skills/howone/04-app-sdk/11-entity-data-access-patterns.md +4 -3
- package/templates/vite/.howone/skills/howone/SKILL.md +48 -8
- package/templates/vite/.howone/skills/howone/references/common-errors.md +27 -0
- package/templates/vite/.howone/skills/howone/references/version-evidence.md +47 -0
- package/templates/vite/.howone/skills/howone/scripts/verify-project.mjs +151 -0
- package/templates/vite/package.json +1 -1
- package/templates/vite/src/App.tsx +9 -5
- package/templates/vite/src/lib/sdk.ts +7 -5
- package/templates/vite/bun.lock +0 -1478
|
@@ -1,41 +1,27 @@
|
|
|
1
|
-
# AI Manifest Handoff
|
|
1
|
+
# AI Manifest Handoff
|
|
2
2
|
|
|
3
|
-
Use this
|
|
4
|
-
|
|
3
|
+
Use this track after `sync_ai_artifacts` and external workflow submission. It is the short handoff
|
|
4
|
+
from `.howone/ai/manifest.json` to `src/lib/sdk.ts`; use `07-ai-action-calls.md` for execution
|
|
5
|
+
semantics and `10-workflow-execute-sse.md` for wire details.
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
call it?**
|
|
7
|
+
## Handoff protocol
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
1. Read the current manifest, not a cached tool response.
|
|
10
|
+
2. Confirm every action has an exact UUID `workflowId`.
|
|
11
|
+
3. Generate Zod input/output schemas from the manifest.
|
|
12
|
+
4. Bind with `defineAiAction` and compose with `withAiActions`.
|
|
13
|
+
5. Run `node .howone/skills/howone/scripts/verify-project.mjs`, typecheck, and build.
|
|
11
14
|
|
|
12
|
-
|
|
15
|
+
Workflow create/update can rotate the config UUID. After every successful external workflow
|
|
16
|
+
operation, sync and re-read the manifest before deciding whether the app binding changed.
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
There is no action-name fallback. A missing UUID is a module initialization error, not a server
|
|
19
|
+
request that can be repaired later.
|
|
16
20
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
1. Read stable action name/ID.
|
|
20
|
-
2. Read `workflowId`.
|
|
21
|
-
3. Read `inputSchema`.
|
|
22
|
-
4. Read `outputSchema`.
|
|
23
|
-
5. Generate Zod input and output schemas.
|
|
24
|
-
6. Bind with `defineAiAction(name, { workflowId, inputSchema, outputSchema })`.
|
|
25
|
-
7. Compose with `withAiActions(client, ai)`.
|
|
26
|
-
|
|
27
|
-
`workflowId` is mandatory. Without it, the SDK falls back to the action name as the execution URL
|
|
28
|
-
segment, and the workflow service will reject it because the segment is not a UUID.
|
|
29
|
-
|
|
30
|
-
## Generated Binding Example
|
|
21
|
+
## Example
|
|
31
22
|
|
|
32
23
|
```ts
|
|
33
|
-
import {
|
|
34
|
-
createClient,
|
|
35
|
-
defineAiAction,
|
|
36
|
-
defineAiActions,
|
|
37
|
-
withAiActions,
|
|
38
|
-
} from '@howone/sdk'
|
|
24
|
+
import { createClient, defineAiAction, defineAiActions, withAiActions } from '@howone/sdk'
|
|
39
25
|
import { z } from 'zod'
|
|
40
26
|
|
|
41
27
|
const client = createClient({
|
|
@@ -43,22 +29,14 @@ const client = createClient({
|
|
|
43
29
|
env: import.meta.env.VITE_HOWONE_ENV,
|
|
44
30
|
})
|
|
45
31
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
summary_length: z.string().optional(),
|
|
49
|
-
})
|
|
50
|
-
export type SummarizeDocumentInput = z.infer<typeof summarizeDocumentInputSchema>
|
|
32
|
+
const inputSchema = z.object({ documentUrl: z.string().url() })
|
|
33
|
+
const outputSchema = z.object({ summary: z.string() })
|
|
51
34
|
|
|
52
|
-
|
|
53
|
-
summary: z.string(),
|
|
54
|
-
})
|
|
55
|
-
export type SummarizeDocumentOutput = z.infer<typeof summarizeDocumentOutputSchema>
|
|
56
|
-
|
|
57
|
-
export const ai = defineAiActions({
|
|
35
|
+
const ai = defineAiActions({
|
|
58
36
|
summarizeDocument: defineAiAction('summarizeDocument', {
|
|
59
37
|
workflowId: '550e8400-e29b-41d4-a716-446655440000',
|
|
60
|
-
inputSchema
|
|
61
|
-
outputSchema
|
|
38
|
+
inputSchema,
|
|
39
|
+
outputSchema,
|
|
62
40
|
}),
|
|
63
41
|
})
|
|
64
42
|
|
|
@@ -66,172 +44,47 @@ const howone = withAiActions(client, ai)
|
|
|
66
44
|
export default howone
|
|
67
45
|
```
|
|
68
46
|
|
|
69
|
-
|
|
47
|
+
The UUID above is illustrative only. Copy the real value from the current manifest.
|
|
48
|
+
|
|
49
|
+
## Schema conversion rules
|
|
70
50
|
|
|
71
51
|
| JSON Schema | Zod |
|
|
72
52
|
|---|---|
|
|
73
|
-
|
|
|
74
|
-
|
|
|
75
|
-
| `number`
|
|
76
|
-
|
|
|
77
|
-
|
|
|
78
|
-
|
|
|
79
|
-
|
|
|
80
|
-
|
|
|
81
|
-
|
|
|
82
|
-
| field not in `required` | `.optional()` |
|
|
83
|
-
| nullable | `.nullable()` |
|
|
84
|
-
|
|
85
|
-
Rules:
|
|
86
|
-
|
|
87
|
-
- Required manifest fields must stay required in Zod.
|
|
88
|
-
- Do not add `.passthrough()` to hide execution envelope problems.
|
|
89
|
-
- Do not make outputs optional to silence validation failures.
|
|
90
|
-
- If the workflow returns a different shape, fix the workflow/capability contract.
|
|
91
|
-
|
|
92
|
-
## Calling Actions
|
|
93
|
-
|
|
94
|
-
For typed one-shot actions:
|
|
95
|
-
|
|
96
|
-
```ts
|
|
97
|
-
const output = await howone.ai.summarizeDocument.run({
|
|
98
|
-
document_url,
|
|
99
|
-
summary_length: 'short',
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
setSummary(output.summary)
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
When `outputSchema` exists, `.run()` returns the validated `finalResult` payload directly.
|
|
106
|
-
|
|
107
|
-
Do not read:
|
|
108
|
-
|
|
109
|
-
```ts
|
|
110
|
-
result.finalResult.summary
|
|
111
|
-
result.data.summary
|
|
112
|
-
result.raw.finalResult
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
Those are execution-envelope paths, not the typed SDK action contract.
|
|
116
|
-
|
|
117
|
-
## Streaming And Events
|
|
118
|
-
|
|
119
|
-
Use `.stream()` when UI needs live output or cancellation:
|
|
120
|
-
|
|
121
|
-
```ts
|
|
122
|
-
const session = howone.ai.generateStory.stream(input, {
|
|
123
|
-
onMessageChunk: (line) => appendLog(line),
|
|
124
|
-
onProgress: (percent, line) => {
|
|
125
|
-
if (line?.startsWith('[DISPLAY]')) setStatus(line.replace('[DISPLAY]', '').trim())
|
|
126
|
-
if (percent === 100) setProgress(100)
|
|
127
|
-
},
|
|
128
|
-
onCreditInsufficient: (event) => showCreditError(event.message),
|
|
129
|
-
onRunError: (event) => showExecutionError(event.message),
|
|
130
|
-
onError: (error) => setError(error.message),
|
|
131
|
-
onComplete: (result) => setRawResult(result),
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
cancelButton.onclick = () => session.cancel()
|
|
135
|
-
const final = await session.result
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
Use `.events()` when code wants an async iterable:
|
|
139
|
-
|
|
140
|
-
```ts
|
|
141
|
-
for await (const event of howone.ai.generateStory.events(input)) {
|
|
142
|
-
if (event.type === 'progress') {
|
|
143
|
-
appendLog(String(event.message))
|
|
144
|
-
}
|
|
145
|
-
if (event.type === 'run_complete') {
|
|
146
|
-
setFinalResult(event.message)
|
|
147
|
-
}
|
|
148
|
-
if (event.type === 'credit_insufficient') {
|
|
149
|
-
showCreditError(String(event.message))
|
|
150
|
-
}
|
|
151
|
-
if (event.type === 'run_error') {
|
|
152
|
-
showExecutionError(String(event.message))
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
## UI State
|
|
158
|
-
|
|
159
|
-
The SDK returns data and exposes callbacks. The app owns all visible UI.
|
|
160
|
-
|
|
161
|
-
Recommended states:
|
|
162
|
-
|
|
163
|
-
```ts
|
|
164
|
-
type AiUiState<T> =
|
|
165
|
-
| { status: 'idle' }
|
|
166
|
-
| { status: 'running'; progress?: number }
|
|
167
|
-
| { status: 'succeeded'; output: T }
|
|
168
|
-
| { status: 'failed'; message: string }
|
|
169
|
-
| { status: 'cancelled' }
|
|
170
|
-
```
|
|
53
|
+
| string | `z.string()` |
|
|
54
|
+
| string URI | `z.string().url()` |
|
|
55
|
+
| number/integer | `z.number()` / `z.number().int()` |
|
|
56
|
+
| boolean | `z.boolean()` |
|
|
57
|
+
| array | `z.array(item)` |
|
|
58
|
+
| object | `z.object({ ... })` |
|
|
59
|
+
| enum | `z.enum([...])` |
|
|
60
|
+
| nullable | `z.nullable(...)` |
|
|
61
|
+
| not required | `.optional()` |
|
|
171
62
|
|
|
172
|
-
Do not
|
|
63
|
+
Do not make required fields optional and do not use `.passthrough()` to conceal a workflow result
|
|
64
|
+
shape mismatch. Typed `.run()` returns the validated final result directly.
|
|
173
65
|
|
|
174
|
-
##
|
|
175
|
-
|
|
176
|
-
If AI output should survive refresh, use entity persistence after the action returns.
|
|
177
|
-
|
|
178
|
-
For history-style products, prefer `runAiActionAndPersist()`:
|
|
66
|
+
## Result handoff
|
|
179
67
|
|
|
180
68
|
```ts
|
|
181
|
-
const
|
|
182
|
-
entity: howone.entities.Generation,
|
|
183
|
-
input: { prompt },
|
|
184
|
-
createPending: (input) => ({
|
|
185
|
-
prompt: input.prompt,
|
|
186
|
-
status: 'pending',
|
|
187
|
-
requestedAt: new Date().toISOString(),
|
|
188
|
-
}),
|
|
189
|
-
run: (input) => howone.ai.generateImage.run(input),
|
|
190
|
-
mapCompleted: ({ output }) => ({
|
|
191
|
-
status: 'completed',
|
|
192
|
-
resultUrl: output.generated_image_url,
|
|
193
|
-
completedAt: new Date().toISOString(),
|
|
194
|
-
}),
|
|
195
|
-
mapFailed: ({ error }) => ({
|
|
196
|
-
status: 'failed',
|
|
197
|
-
errorMessage: error instanceof Error ? error.message : 'Generation failed',
|
|
198
|
-
}),
|
|
199
|
-
})
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
For simple save-after-success:
|
|
203
|
-
|
|
204
|
-
```ts
|
|
205
|
-
const output = await howone.ai.summarizeDocument.run(input)
|
|
69
|
+
const output = await howone.ai.summarizeDocument.run({ documentUrl })
|
|
206
70
|
await howone.entities.DocumentSummary.create({
|
|
207
|
-
documentUrl
|
|
71
|
+
documentUrl,
|
|
208
72
|
summary: output.summary,
|
|
209
73
|
status: 'completed',
|
|
210
74
|
})
|
|
211
75
|
```
|
|
212
76
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
## Workflow Edit Handoff
|
|
216
|
-
|
|
217
|
-
When changing external workflow behavior later:
|
|
218
|
-
|
|
219
|
-
1. If schema changes, update AI capability contract first and sync manifest.
|
|
220
|
-
2. Submit update through `external-ai-capability` with `updates: [{ capabilityName, updatePrompt }]`.
|
|
221
|
-
3. Re-read `.howone/ai/manifest.json`; update may rotate `workflowId` to a fresh config UUID.
|
|
222
|
-
4. Regenerate SDK bindings whenever the manifest `workflowId`, input schema, or output schema changed.
|
|
223
|
-
5. Behavior-only updates still require checking the manifest before deciding SDK is unchanged.
|
|
77
|
+
For history, use pending-first `runAiActionAndPersist`. The workflow does not perform database CRUD;
|
|
78
|
+
the app persists through generated entity bindings.
|
|
224
79
|
|
|
225
|
-
|
|
226
|
-
|
|
80
|
+
For streaming, use `onMessageChunk`, await `session.result`, and catch terminal workflow errors.
|
|
81
|
+
For events, a terminal error event is yielded before the iterator rejects.
|
|
227
82
|
|
|
228
|
-
##
|
|
83
|
+
## Final checklist
|
|
229
84
|
|
|
230
|
-
-
|
|
231
|
-
-
|
|
232
|
-
- Zod
|
|
233
|
-
- `
|
|
234
|
-
-
|
|
235
|
-
-
|
|
236
|
-
- Persistence goes through `howone.entities.*`.
|
|
237
|
-
- Visible status/error UI is app-owned.
|
|
85
|
+
- [ ] Current AI manifest was read after the last sync.
|
|
86
|
+
- [ ] Action IDs and UUIDs match exactly.
|
|
87
|
+
- [ ] Zod requiredness/enums match the manifest.
|
|
88
|
+
- [ ] No `aiUrl`, `aiBaseUrl`, or raw EAX URL was added.
|
|
89
|
+
- [ ] App uses `HowOneProvider client={howone}`.
|
|
90
|
+
- [ ] Verifier, typecheck, and build pass.
|
|
@@ -86,7 +86,7 @@ Use callbacks instead:
|
|
|
86
86
|
|
|
87
87
|
```tsx
|
|
88
88
|
<HowOneProvider
|
|
89
|
-
|
|
89
|
+
client={howone}
|
|
90
90
|
brand="visible"
|
|
91
91
|
onAuthRedirect={({ mode, returnUrl }) => {
|
|
92
92
|
setAuthUi({ redirecting: true, mode, returnUrl })
|
|
@@ -102,8 +102,8 @@ Use callbacks instead:
|
|
|
102
102
|
Hide the logo only when explicitly requested:
|
|
103
103
|
|
|
104
104
|
```tsx
|
|
105
|
-
<HowOneProvider brand="hidden" />
|
|
106
|
-
<HowOneProvider showBrandButton={false} />
|
|
105
|
+
<HowOneProvider client={howone} brand="hidden" />
|
|
106
|
+
<HowOneProvider client={howone} showBrandButton={false} />
|
|
107
107
|
```
|
|
108
108
|
|
|
109
109
|
## UI Feedback Rule
|
|
@@ -132,7 +132,7 @@ For streaming workflows, use callbacks/events:
|
|
|
132
132
|
const session = howone.ai.generateImage.stream(
|
|
133
133
|
{ prompt },
|
|
134
134
|
{
|
|
135
|
-
|
|
135
|
+
onMessageChunk: (delta) => appendLog(delta),
|
|
136
136
|
onProgress: (progress) => setProgress(progress),
|
|
137
137
|
onError: (error) => setStatus({ type: 'error', message: error.message }),
|
|
138
138
|
onComplete: (result) => setResult(result.finalResult),
|
|
@@ -1,52 +1,40 @@
|
|
|
1
1
|
# Workflow Execute SSE
|
|
2
2
|
|
|
3
|
-
Use this reference
|
|
3
|
+
Use this reference when an app needs live workflow progress, cancellation, or raw event iteration.
|
|
4
|
+
For ordinary AI actions, start with `07-ai-action-calls.md` and use the typed action methods.
|
|
4
5
|
|
|
5
|
-
##
|
|
6
|
+
## Environment and endpoint
|
|
6
7
|
|
|
7
|
-
The SDK
|
|
8
|
+
The SDK chooses the EAX base from `createClient({ env })`:
|
|
8
9
|
|
|
9
|
-
|
|
|
10
|
+
| `env` | EAX base |
|
|
10
11
|
|---|---|
|
|
11
|
-
| `
|
|
12
|
+
| `local` | `https://eax-backend-orchestrator-dev.fly.dev` |
|
|
13
|
+
| `dev` | `https://eax-backend-orchestrator-dev.fly.dev` |
|
|
14
|
+
| `prod` | `https://eax-backend-orchestrator-prod.fly.dev` |
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
Do not construct this URL in app code or override it with `aiUrl`. A workflow action calls:
|
|
14
17
|
|
|
15
|
-
```
|
|
16
|
-
{
|
|
17
|
-
"inputs": {},
|
|
18
|
-
"priority": "normal"
|
|
19
|
-
}
|
|
18
|
+
```text
|
|
19
|
+
POST {eaxBase}/workflow/execute/{projectId}/{workflowId}
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
`priority
|
|
23
|
-
|
|
24
|
-
The successful HTTP response includes:
|
|
25
|
-
|
|
26
|
-
| Header | Meaning |
|
|
27
|
-
|---|---|
|
|
28
|
-
| `Content-Type: text/event-stream` | Streaming response |
|
|
29
|
-
| `X-Run-Id` | Execution task id for status/cancel follow-up |
|
|
30
|
-
|
|
31
|
-
Do not use or generate code for old endpoints such as
|
|
32
|
-
`/workflow/{appId}/{workflowId}/execute_sse`.
|
|
22
|
+
The request body is `{ inputs: {}, priority?: 'low' | 'normal' | 'high' }` and carries the current
|
|
23
|
+
client token as `Authorization: Bearer ...`.
|
|
33
24
|
|
|
34
|
-
## Wire
|
|
25
|
+
## Wire protocol
|
|
35
26
|
|
|
36
|
-
Frames
|
|
27
|
+
Frames are JSON `data:` lines separated by a blank line. The JSON envelope owns the ID and event
|
|
28
|
+
name; there are no separate legacy SSE `id:` or `event:` lines:
|
|
37
29
|
|
|
38
30
|
```text
|
|
39
|
-
data: {"id":"
|
|
31
|
+
data: {"id":"evt_1","event":"run_start","message":"execution with sse is started"}
|
|
40
32
|
|
|
41
|
-
data: {"id":"
|
|
33
|
+
data: {"id":"evt_2","event":"progress","message":"[DISPLAY] Executing node: Extract"}
|
|
42
34
|
|
|
43
|
-
data: {"id":"
|
|
35
|
+
data: {"id":"evt_3","event":"run_complete","message":{"imageUrl":"https://..."}}
|
|
44
36
|
```
|
|
45
37
|
|
|
46
|
-
There are no separate SSE `event:` or `id:` lines. The JSON envelope owns those fields.
|
|
47
|
-
|
|
48
|
-
## Envelope
|
|
49
|
-
|
|
50
38
|
```ts
|
|
51
39
|
type WorkflowSseEnvelope = {
|
|
52
40
|
id: string
|
|
@@ -55,51 +43,96 @@ type WorkflowSseEnvelope = {
|
|
|
55
43
|
}
|
|
56
44
|
```
|
|
57
45
|
|
|
58
|
-
Event meanings:
|
|
59
|
-
|
|
60
|
-
| Event | Message | Meaning |
|
|
61
|
-
|---|---|---|
|
|
62
|
-
| `run_start` | string | Stream opened and execution started. |
|
|
63
|
-
| `progress` | string | One worker log line. `[DISPLAY]` lines are intended for UI. |
|
|
64
|
-
| `run_complete` | object | Final workflow-specific output. SDK maps this to `AiResult.finalResult`. |
|
|
65
|
-
| `run_error` | string | Workflow failed. |
|
|
66
|
-
| `credit_insufficient` | string | Billing/credit block. |
|
|
67
|
-
|
|
68
46
|
The terminal event is exactly one of `run_complete`, `run_error`, or `credit_insufficient`.
|
|
69
|
-
There is no `stream_end` event
|
|
70
|
-
|
|
71
|
-
## SDK Mapping
|
|
72
|
-
|
|
73
|
-
`howone.ai.run(configId, inputs)` and typed action `.run()` call the endpoint above.
|
|
47
|
+
There is no `stream_end`, `node_start`, `tool_call_end`, or `ai_message_chunk` event in this
|
|
48
|
+
workflow endpoint.
|
|
74
49
|
|
|
75
|
-
|
|
50
|
+
## SDK mapping
|
|
76
51
|
|
|
77
52
|
```ts
|
|
78
53
|
type AiResult = {
|
|
79
54
|
success: boolean
|
|
80
|
-
runId?: string
|
|
55
|
+
runId?: string // X-Run-Id response header
|
|
81
56
|
outcome: 'success' | 'credit_insufficient' | 'run_error' | null
|
|
82
57
|
finalResult: Record<string, unknown> | null
|
|
83
58
|
progressLogs: string[]
|
|
59
|
+
stateData: Record<string, unknown> // deprecated; always empty for this protocol
|
|
60
|
+
nodeExecutions: unknown[] // deprecated; always empty
|
|
61
|
+
toolExecutions: unknown[] // deprecated; always empty
|
|
62
|
+
totalDuration: number
|
|
84
63
|
errors: string[]
|
|
85
64
|
events: AiEvent[]
|
|
86
65
|
}
|
|
87
66
|
```
|
|
88
67
|
|
|
89
|
-
|
|
90
|
-
|
|
68
|
+
`run_complete.message` becomes `finalResult`. `progress.message` lines append to `progressLogs`.
|
|
69
|
+
`run_error` and `credit_insufficient` append errors and cause `run()`/`stream.result` to reject with
|
|
70
|
+
`WorkflowExecutionError`; the `events()` iterator yields the terminal event before rejecting.
|
|
71
|
+
|
|
72
|
+
## UI pattern
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
function WorkflowProgress({ input }: { input: GenerateInput }) {
|
|
76
|
+
const [logs, setLogs] = useState<string[]>([])
|
|
77
|
+
const [error, setError] = useState<string | null>(null)
|
|
78
|
+
|
|
79
|
+
async function run() {
|
|
80
|
+
const controller = new AbortController()
|
|
81
|
+
try {
|
|
82
|
+
const result = await howone.ai.generate.run(input, {
|
|
83
|
+
signal: controller.signal,
|
|
84
|
+
onMessageChunk: (line) => setLogs((current) => [...current, line]),
|
|
85
|
+
})
|
|
86
|
+
console.log(result)
|
|
87
|
+
} catch (value) {
|
|
88
|
+
setError(value instanceof Error ? value.message : String(value))
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return <button onClick={() => void run()}>Run workflow</button>
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
For a cancel button, keep the controller/session in a ref and call `abort()` or `session.cancel()`.
|
|
97
|
+
Treat abort as a user action in UI; do not report it as an EAX `run_error`.
|
|
91
98
|
|
|
92
|
-
## Callback
|
|
99
|
+
## Callback rules
|
|
93
100
|
|
|
94
|
-
- `
|
|
95
|
-
- `
|
|
96
|
-
- `
|
|
97
|
-
- `
|
|
98
|
-
|
|
99
|
-
- `
|
|
100
|
-
- `
|
|
101
|
+
- `onEvent(event, 'workflow')` fires for each parsed envelope.
|
|
102
|
+
- `onRunStart` fires for `run_start`.
|
|
103
|
+
- `onMessageChunk(text, event)` fires for each non-empty progress line.
|
|
104
|
+
- `onProgress(0, message)` is a compatibility callback for progress lines; `onProgress(100)` fires
|
|
105
|
+
on successful completion.
|
|
106
|
+
- `onRunComplete` receives the final object.
|
|
107
|
+
- `onRunError` and `onCreditInsufficient` receive terminal messages.
|
|
108
|
+
- `onComplete` receives the assembled result before a terminal failure is rejected.
|
|
109
|
+
- `onError` receives transport and terminal errors.
|
|
101
110
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
111
|
+
Callbacks are not a substitute for awaiting `run()` or `session.result`; always handle the promise.
|
|
112
|
+
The SDK retains at most 1,000 events and 500 progress lines by default while still invoking callbacks
|
|
113
|
+
for every event. Configure the bounds for a diagnostic screen rather than retaining unbounded logs.
|
|
105
114
|
|
|
115
|
+
## Raw event iteration
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
try {
|
|
119
|
+
for await (const event of howone.ai.generate.events(input)) {
|
|
120
|
+
renderEvent(event)
|
|
121
|
+
}
|
|
122
|
+
} catch (error) {
|
|
123
|
+
renderFailure(error)
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
On a terminal failure, consumers see the `run_error` or `credit_insufficient` event before the
|
|
128
|
+
iterator rejects. Breaking the loop cancels the underlying reader.
|
|
129
|
+
|
|
130
|
+
## Common mistakes
|
|
131
|
+
|
|
132
|
+
| Mistake | Fix |
|
|
133
|
+
|---|---|
|
|
134
|
+
| Calling the old `/workflow/{appId}/{id}/execute_sse` path | Use the typed SDK action. |
|
|
135
|
+
| Adding `/api` to EAX URL | EAX base is not the REST base; app code should not build it. |
|
|
136
|
+
| Using `onStreamChunk`/`onTextChunk` | Use `onMessageChunk`. |
|
|
137
|
+
| Treating `run_error` as a successful result | Catch `WorkflowExecutionError` and inspect `outcome`. |
|
|
138
|
+
| Parsing `event.payload.result` | Read `event.message`; `run_complete` message is the final object. |
|
|
@@ -189,7 +189,7 @@ Schema:
|
|
|
189
189
|
"create": "scoped",
|
|
190
190
|
"update": "none",
|
|
191
191
|
"delete": "none",
|
|
192
|
-
"requiredScopes": ["
|
|
192
|
+
"requiredScopes": ["submissionKey"],
|
|
193
193
|
"allowedFilters": [],
|
|
194
194
|
"allowedSorts": [],
|
|
195
195
|
"defaultLimit": 1,
|
|
@@ -203,7 +203,7 @@ Frontend:
|
|
|
203
203
|
|
|
204
204
|
```ts
|
|
205
205
|
await howone.public.entities.Feedback.create({
|
|
206
|
-
|
|
206
|
+
submissionKey,
|
|
207
207
|
message,
|
|
208
208
|
rating,
|
|
209
209
|
})
|
|
@@ -211,7 +211,8 @@ await howone.public.entities.Feedback.create({
|
|
|
211
211
|
|
|
212
212
|
Rules:
|
|
213
213
|
|
|
214
|
-
- Public create
|
|
214
|
+
- Public create must use the declared public scope/business fields. Never pass ownership/system
|
|
215
|
+
fields such as `created_by_user_id`; the SDK rejects them and the backend derives ownership/scope.
|
|
215
216
|
- Do not expose public read unless needed.
|
|
216
217
|
- Add anti-abuse UX/server constraints outside the dynamic schema when needed.
|
|
217
218
|
- Never persist UI-only fields from form components.
|
|
@@ -13,6 +13,24 @@ HowOne builds generated full-stack AI apps. Platform contracts are separate from
|
|
|
13
13
|
|
|
14
14
|
Load only the track needed for the user's request. Do not read SDK files while designing backend or AI contracts unless the task has reached manifest-to-code implementation.
|
|
15
15
|
|
|
16
|
+
## Version Evidence (Mastra-style)
|
|
17
|
+
|
|
18
|
+
Before writing app SDK code, establish this evidence chain:
|
|
19
|
+
|
|
20
|
+
```text
|
|
21
|
+
synced project manifests
|
|
22
|
+
→ app package.json SDK range
|
|
23
|
+
→ installed @howone/sdk version
|
|
24
|
+
→ installed dist/*.d.ts and runtime exports
|
|
25
|
+
→ this Skill's matching recipe
|
|
26
|
+
→ remote docs only as supplemental context
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Read `references/version-evidence.md` and run
|
|
30
|
+
`node .howone/skills/howone/scripts/verify-project.mjs` from the app root. If the requested and
|
|
31
|
+
installed SDK versions disagree, install dependencies before coding. Never solve a version mismatch
|
|
32
|
+
with `any`, guessed signatures, or copied latest documentation.
|
|
33
|
+
|
|
16
34
|
## Trigger Preconditions
|
|
17
35
|
|
|
18
36
|
Use this skill before work when any condition is true:
|
|
@@ -100,7 +118,8 @@ AI contract:
|
|
|
100
118
|
|
|
101
119
|
```text
|
|
102
120
|
get_current_ai_capabilities -> apply_capability_patch -> sync_ai_artifacts -> external-ai-capability
|
|
103
|
-
->
|
|
121
|
+
-> continue independent app implementation (never end merely to wait)
|
|
122
|
+
-> terminal result -> sync_ai_artifacts -> read .howone/ai/manifest.json
|
|
104
123
|
```
|
|
105
124
|
|
|
106
125
|
No contract dry-run step. Normal generation applies one well-formed patch directly. For destructive,
|
|
@@ -110,10 +129,14 @@ approved patch.
|
|
|
110
129
|
SDK/code:
|
|
111
130
|
|
|
112
131
|
```text
|
|
113
|
-
read synced manifests -> choose auth posture -> update src/lib/sdk.ts
|
|
114
|
-
-> implement UI calls using
|
|
132
|
+
read version evidence -> read synced manifests -> choose auth posture -> update src/lib/sdk.ts
|
|
133
|
+
-> install HowOneProvider (client={howone}) -> implement UI calls using generated bindings
|
|
134
|
+
-> run project verifier -> typecheck/build
|
|
115
135
|
```
|
|
116
136
|
|
|
137
|
+
For fragile operations, prefer the deterministic verifier and the operation recipes over retyping
|
|
138
|
+
transport details in an app. Run the verifier again after manifest or dependency changes.
|
|
139
|
+
|
|
117
140
|
## App Runtime Activation Gate
|
|
118
141
|
|
|
119
142
|
Classify runtime activation before editing the application shell:
|
|
@@ -125,13 +148,13 @@ Classify runtime activation before editing the application shell:
|
|
|
125
148
|
one module-level client in `src/lib/sdk.ts`, import it before Provider initialization, and wrap the
|
|
126
149
|
application root in `HowOneProvider`.
|
|
127
150
|
- HowOne AI, uploads, and `howone.entities.*` authenticated/private access require a real token path.
|
|
128
|
-
|
|
129
|
-
|
|
151
|
+
Use the default hosted client and pass the composed singleton to
|
|
152
|
+
`<HowOneProvider client={howone}>`; its required guard is inherited from `client.auth.guard`.
|
|
130
153
|
- A custom HowOne login requires `createClient({ auth: 'custom', loginPath })`, a real OTP/OAuth flow
|
|
131
|
-
that writes the returned token through `howone.auth.setToken()`, and
|
|
132
|
-
|
|
154
|
+
that writes the returned token through `howone.auth.setToken()`, and a Provider receiving that
|
|
155
|
+
client. A login-looking screen without token acquisition is not completion.
|
|
133
156
|
- An app that uses only manifest-approved `howone.public.entities.*` access may use
|
|
134
|
-
`
|
|
157
|
+
`auth: 'none'`; the Provider inherits a non-required guard and must not force login.
|
|
135
158
|
|
|
136
159
|
Do not delete the scaffold Provider while replacing `App.tsx` or `main.tsx` when the HowOne runtime
|
|
137
160
|
is active. Before completion, verify the Provider still wraps the rendered app, the SDK module is
|
|
@@ -144,6 +167,7 @@ data, local-only persistence, or unauthenticated fallback behavior.
|
|
|
144
167
|
- Backend fields/access/indexes: `{appRoot}/.howone/database/manifest.json` after sync.
|
|
145
168
|
- AI names/workflow IDs/schemas: `{appRoot}/.howone/ai/manifest.json` after sync.
|
|
146
169
|
- App runtime entry: `{appRoot}/src/lib/sdk.ts`.
|
|
170
|
+
- Installed SDK typings/runtime: `{appRoot}/node_modules/@howone/sdk/dist/*` after package install.
|
|
147
171
|
- Do not handwrite `.howone/` metadata.
|
|
148
172
|
- Do not infer contract identifiers from prompts, memory, or dependency source.
|
|
149
173
|
|
|
@@ -196,6 +220,14 @@ Do not read unrelated capability references.
|
|
|
196
220
|
| `04-app-sdk/11-entity-data-access-patterns.md` | App entity access calls from synced manifest |
|
|
197
221
|
| `04-app-sdk/12-query-dsl-and-responses.md` | App query/filter/sort/pagination calls |
|
|
198
222
|
|
|
223
|
+
### References and scripts
|
|
224
|
+
|
|
225
|
+
| Resource | Use |
|
|
226
|
+
|---|---|
|
|
227
|
+
| `references/version-evidence.md` | Verify installed SDK version and declaration/runtime evidence before coding |
|
|
228
|
+
| `references/common-errors.md` | Diagnose stale signatures, routing, auth, query, upload, and AI failures |
|
|
229
|
+
| `scripts/verify-project.mjs` | Deterministically check project wiring and manifest workflow IDs |
|
|
230
|
+
|
|
199
231
|
## Hard Rules
|
|
200
232
|
|
|
201
233
|
- Backend and AI design references must not include SDK implementation work.
|
|
@@ -206,6 +238,14 @@ Do not read unrelated capability references.
|
|
|
206
238
|
- An active HowOne app runtime must include `src/lib/sdk.ts`, `HowOneProvider`, and the auth posture
|
|
207
239
|
required by its AI/entity/upload access. Contract creation without usable authenticated app wiring
|
|
208
240
|
is incomplete.
|
|
241
|
+
- New app code must pass the composed singleton to `<HowOneProvider client={howone}>`; the Provider
|
|
242
|
+
derives its default route guard from `client.auth.guard`.
|
|
243
|
+
- EAX workflow URLs are owned by `env`: `local/dev` use
|
|
244
|
+
`https://eax-backend-orchestrator-dev.fly.dev`, and `prod` uses
|
|
245
|
+
`https://eax-backend-orchestrator-prod.fly.dev`. Do not add `aiUrl`, `aiBaseUrl`, or a raw EAX URL.
|
|
246
|
+
- `howone.raw.*` resolves to response data, not an AxiosResponse. Its REST base already includes
|
|
247
|
+
`/api`; app-owned paths must not repeat that prefix.
|
|
248
|
+
- Use `@howone/sdk/devtools` for `ElementSelectorProvider`; keep core free of React imports.
|
|
209
249
|
- AI workflows must not perform database CRUD; persistence is app code through entities.
|
|
210
250
|
- Detailed capability references constrain workflow design; they do not replace synced manifests or
|
|
211
251
|
define direct app SDK calls.
|