@foldspace_npm/harness 0.1.11 → 0.1.13

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 (38) hide show
  1. package/CLAUDE.md +287 -178
  2. package/README.md +1 -1
  3. package/package.json +2 -1
  4. package/recipes/INDEX.md +15 -0
  5. package/recipes/README.md +46 -0
  6. package/recipes/find-by-name/README.md +47 -0
  7. package/recipes/find-by-name/agent/actions/find_project_id.ts +101 -0
  8. package/recipes/find-by-name/agent/api/projects.ts +36 -0
  9. package/recipes/find-by-name/agent/projects.ts +40 -0
  10. package/recipes/find-by-name/fixtures/projects.all.json +29 -0
  11. package/recipes/find-by-name/fixtures/projects.empty-account.json +4 -0
  12. package/recipes/find-by-name/fixtures/projects.none.json +4 -0
  13. package/recipes/find-by-name/recipe.json +10 -0
  14. package/recipes/pick-from-a-list/README.md +44 -0
  15. package/recipes/pick-from-a-list/agent/actions/choose_project.ts +161 -0
  16. package/recipes/pick-from-a-list/agent/api/projects.ts +36 -0
  17. package/recipes/pick-from-a-list/agent/projects.ts +40 -0
  18. package/recipes/pick-from-a-list/agent/views/brand.ts +14 -0
  19. package/recipes/pick-from-a-list/agent/views/picker.ts +119 -0
  20. package/recipes/pick-from-a-list/fixtures/projects.all.json +29 -0
  21. package/recipes/pick-from-a-list/fixtures/projects.empty-account.json +4 -0
  22. package/recipes/pick-from-a-list/recipe.json +10 -0
  23. package/recipes/swap-the-login-method/README.md +40 -0
  24. package/recipes/swap-the-login-method/agent/utils.ts +73 -0
  25. package/recipes/swap-the-login-method/fixtures/anything.ok.json +8 -0
  26. package/recipes/swap-the-login-method/recipe.json +12 -0
  27. package/recipes/swap-the-login-method/variants/utils.cookies.ts +64 -0
  28. package/recipes/who-is-the-user/README.md +56 -0
  29. package/recipes/who-is-the-user/agent/identify.ts +89 -0
  30. package/recipes/who-is-the-user/fixtures/profile.ok.json +6 -0
  31. package/recipes/who-is-the-user/recipe.json +9 -0
  32. package/src/runtime/config.ts +1 -1
  33. package/src/runtime/http.ts +104 -53
  34. package/src/runtime/index.ts +4 -1
  35. package/src/runtime/match.ts +1 -1
  36. package/src/runtime/render.ts +42 -1
  37. package/templates/agent-starter/agent/actions/_example.ts +9 -2
  38. package/templates/agent-starter/agent/utils.ts +2 -0
package/CLAUDE.md CHANGED
@@ -1,108 +1,184 @@
1
1
  # Foldspace Agent
2
2
 
3
- ## Harness version
4
-
5
- At the start of a session, run `npx foldspace help --json` (or, from an older
6
- install, `npx --yes @foldspace_npm/harness@latest upgrade --check`).
7
-
8
- If `update.outdated` is true, tell the user the pinned and latest versions and
9
- **ask before upgrading**. After they agree, run `foldspace upgrade --yes`, then
10
- `npm run build`. Do not upgrade as a side effect of build, attach, or
11
- implementing an action. A harness bump is not a deploy.
12
-
13
- If the tenant `CLAUDE.md` does not contain
14
- `@node_modules/@foldspace_npm/harness/CLAUDE.md`, ask before
15
- `foldspace upgrade --refresh-instructions`. That replaces the snapshot with an
16
- import of this file.
17
-
18
- ## Before building
19
-
20
- 1. Run `npx foldspace help --json` before an unfamiliar harness operation.
21
- Treat its risk, prerequisites, effects, next-step, and `update` fields as
22
- the current CLI contract.
23
- 2. Connect Product MCP and verify that `list_agents` works.
24
- 3. Inspect the selected agent with `get_agent_settings`, `list_actions`, and
25
- `list_task_agents`.
26
- 4. Use `discover_actions` to identify candidate experiences, then let the user
27
- choose what to build. Treat those ideas as candidates, not an inventory.
28
-
29
- ## Build workflow
30
-
31
- An experience can require several reusable actions. Separate lookup actions
32
- from actions that read or mutate a selected resource.
33
-
34
- 1. Agree on the candidate experience and how its actions would compose. This is
35
- not a publish and not a commitment.
36
- 2. Run `npm run inject`. Ask the user to sign in and perform the real target
37
- workflow in that Chrome window.
38
- 3. Capture at least one real HTTP 200 for the data the action needs. Use
39
- chrome-devtools MCP against the inject Chrome, or a page-context `fetch`,
40
- **before** `attach` owns the debug port. If there is no 200,
41
- pivot; do not create Foldspace resources. Write what you establish into
42
- `docs/app-profile.md` with **how you know it**. Do not promote an assumption
43
- by quoting that file.
44
- 4. Create action metadata as a draft. `generate_action_handler` works from a
45
- draft schema; do not publish yet.
46
- 5. Implement the handler using the observed request and response shapes.
47
- Import helpers from `agent/utils.ts` inspect that file before writing
48
- another `fetch` or rank helper.
49
- 6. Register the handler in `agent/actions/index.ts` and build (`foldspace build` lints first).
50
- 7. Run `npx foldspace attach --daemon` (add `--bootstrap` or `--replace` when
51
- the page requires it). An empty local registry is valid if you only want to
52
- see how the agent works.
53
- 8. Ask before publishing. Publishing is required only so the copilot can call
54
- the action, and it is a live product change when the agent has real users.
55
- 9. Complete the verification gates below.
56
-
57
- ## API rules
58
-
59
- Actions execute in the user's signed-in browser session.
60
-
61
- - Import HTTP, ranking, and widget helpers from `../utils`, not a new
62
- `agent/api.ts` copy of `fetch`.
63
- - Do not guess `API_BASE` or `AUTH_SOURCE`. Capture at least one real 200
64
- (and the auth header the page actually sends) before filling them in.
65
- `credentials: "include"` is correct only when you have observed the app
66
- using cookies that way. Many apps use `Authorization: Bearer` from
67
- `localStorage` instead; some use a custom header.
68
- - Never guess endpoints or schemas. Capture at least one real 200 before
69
- implementing a parser. Do not implement a path that was not observed.
70
- - Verify that the user is signed in before observing a workflow.
71
- - Do not substitute a public developer API when the browser session is missing;
72
- ask the user to sign in.
73
- - Validate parameters and return sanitized errors. Return **data only** — never
74
- `directive`, `instructions`, or a paragraph telling the copilot what to say.
75
- Action and agent instructions live in Agent Studio / MCP. Never return
76
- `ApiResult.detail` from `execute` — it is for the console.
77
- - Actions that return data the user will inspect should include a `render`
78
- function for in-chat UI (a chatterblock). If it makes more sense to output the data
79
- in a UI component instead of text then consider using render to show a component.
80
- - `render` receives **`execute`'s return value**, not the action's input params.
81
- Returning anything that lacks the ids the card needs is why widgets pass in
82
- isolation and fail in the real chat.
83
- - `runAction` refuses render actions (`cannot be executed silently`). Driving
84
- `render()` yourself never runs `execute()`, so it does not test that contract.
85
- - Check https://docs.foldspace.ai/guides/in-chat-ui/ for more information
86
-
87
- ## Task agents
88
-
89
- Use a Task Agent when the handler needs a one-time LLM subtask that
90
- deterministic code cannot do well: extraction, summarization,
91
- classification, normalization, enrichment, or generation.
92
-
93
- Do not use a Task Agent for API calls, CRUD, routing, or parsing a
94
- known response shape. Those stay in `execute`.
95
-
96
- Task agents are created in Agent Studio, not in this repo. Ask before
97
- creating or publishing one. Call a published task agent from the
98
- handler with `runTask({ taskKey, data })` (if not published the runTask won't work).
99
- `data` carries extracted facts only not `prompt` / `instructions` strings.
100
- Prefer JSON output when the handler must consume the result.
101
-
102
- See https://docs.foldspace.ai/user-guides/task-agents/ and
103
- https://docs.foldspace.ai/reference/task-agent-api/
104
-
105
- ## Local harness loop
3
+ You are adding an AI agent to a product that already exists. It answers
4
+ questions and *does things* inside that product, with the user's own session.
5
+
6
+ The person you are working with may not be a developer. **You run the
7
+ commands; they sign in and talk to the agent.** Their first session ends at
8
+ something they can **see on their own page** not at "it builds".
9
+
10
+ ## Every session starts the same way
11
+
12
+ 1. **Harness version.** Run `npx foldspace help --json` (from an older install,
13
+ `npx --yes @foldspace_npm/harness@latest upgrade --check`). If
14
+ `update.outdated` is true, say the pinned and latest versions and **ask
15
+ before upgrading**; then `foldspace upgrade --yes` and `npm run build`. Never
16
+ upgrade as a side effect of build, attach or implementing an action. If the
17
+ project `CLAUDE.md` does not contain
18
+ `@node_modules/@foldspace_npm/harness/CLAUDE.md`, ask before
19
+ `foldspace upgrade --refresh-instructions`.
20
+ 2. **Is Foldspace connected? Check never assume.** Call `list_agents`:
21
+
22
+ | Result | Do |
23
+ |---|---|
24
+ | The tool does not exist | The Foldspace MCP is not installed in this editor. Give the one install step for *this* editor — Claude Code: `claude plugin install foldspace-agent-builder@foldspace-plugins`; anything else: the MCP block on <https://foldspace.ai/agents.md> — then check again |
25
+ | It exists but fails on auth | Installed, not signed in. Only the human can approve sign-in. Tell them exactly where — Claude Code: `/mcp` → **foldspace** → finish in the browser — then check again |
26
+ | It returns agents | One agent: state its name, Agent Key and product in a line and carry on; the human objects if it is wrong. Several: ask which, as a choice |
27
+
28
+ 3. **Orient** — `get_agent_settings`, `list_actions`, `list_task_agents`,
29
+ `list_navigation_routes`. Record current versions before changing anything;
30
+ if something breaks, this is how you prove what moved. Conversation starters
31
+ seeded at sign-up are often a specification someone already wrote.
32
+ 4. **Read `docs/app-profile.md`.** It is what is already known about this app,
33
+ with how each fact was established.
34
+
35
+ Before an unfamiliar harness operation, `npx foldspace help --json` is the
36
+ current CLI contract risk, prerequisites, effects, next step.
37
+
38
+ ## The first session has a fixed order
39
+
40
+ | Level | Reached when |
41
+ |---|---|
42
+ | **L0 · agent live** | injected, visible on the page, `identify` fired |
43
+ | **L1 · read settings** | the user's own account, plan or settings in a widget, showing their real values |
44
+ | **L2 · look up entities** | a search from the user's own words, showing rows |
45
+
46
+ 1. **Get the agent live first (L0) - with no actions.** Straight after
47
+ orienting: `npm run build`, `npm run inject`, the human signs in to their
48
+ app, then `npx foldspace attach --daemon`. An empty registry is valid. L0
49
+ is `registration_ok` with the agent visible on their page - tell them to
50
+ look at it and say hello to it. **Do not explore the app first**: no
51
+ reading its bundle, no watching its traffic, no browsing its screens.
52
+ Nothing about L0 needs any of it, and minutes of silent investigation
53
+ before the human has seen anything is the wrong first impression.
54
+ 2. **Then offer experiences - from what you already have.** With the agent on
55
+ their screen, offer three or four read-only experiences as a choice,
56
+ recommended first. Sources: the conversation starters seeded at sign-up,
57
+ `discover_actions`, and `docs/app-profile.md`. `discover_actions` gives
58
+ candidates, not an inventory. Do not investigate the app to build this
59
+ list. Order them cheapest first: the user's own data with no parameter
60
+ (L1) before a search with one parameter (L2).
61
+ 3. **Only then observe - and only what the chosen experience needs** (**Find
62
+ the call yourself**). Check the row count as part of that: an experience
63
+ on a resource the account has zero of is a demo of an empty state, so say
64
+ so and offer the next one. On an empty account the useful first action is
65
+ one that creates data - say so instead.
66
+
67
+ Navigation, uploads, extraction and anything that writes are a later session.
68
+
69
+ ### Ask as a choice, and ask almost nothing
70
+
71
+ Every question to the human is an **enumerated choice with the recommended
72
+ option first** (in Claude Code, `AskUserQuestion`). A first session has four
73
+ decision points: which agent (only if several), the app's hostname, which
74
+ experience first, and whether to publish. The hostname is the only free-text
75
+ question. Everything else attach mode, folder, name, level you **state and
76
+ move on**.
77
+
78
+ Then agree the experience in the customer's words, before creating anything:
79
+ *"Show my plan and credits needs one thing the agent can call and one card to
80
+ show it in. Sound right?"*
81
+
82
+ ## Find the call yourself
83
+
84
+ Never guess `API_BASE`, `AUTH_SOURCE`, an endpoint or a schema. But guessing
85
+ and asking are not the only options — **observing is, and it comes first**:
86
+
87
+ 1. **Already known** — `docs/app-profile.md`. A verified endpoint needs no
88
+ browsing.
89
+ 2. **You drive - once they have chosen an experience.** The agent is already
90
+ attached from L0, and `attach` owns the debug port: `npx foldspace attach
91
+ --stop` first. Then *you* go to the one screen that experience needs: read
92
+ the app's bundle for API paths and watch the requests it makes
93
+ (chrome-devtools MCP against the inject Chrome, or a page-context
94
+ `fetch`). Attach again when you have the call. Read the auth method off a request
95
+ the page already sent — bearer from `localStorage`, cookie, custom header.
96
+ Delegate the watching to a read-only subagent so the traffic does not flood
97
+ your context.
98
+ 3. **Ask the human to perform the workflow** only for what you cannot reach:
99
+ an MFA step, a paywall, a screen that needs a record the account does not
100
+ have, or anything that would write.
101
+
102
+ Signing in is the human's step, because `inject` launches a fresh profile.
103
+ Navigating is yours. **While you drive a signed-in account you are read-only:**
104
+ GET only, never submit a form, never click anything that creates, sends or
105
+ deletes.
106
+
107
+ Capture at least one real HTTP 200 for the data the action needs. If there is
108
+ none, pivot; do not create Foldspace resources. Write what you establish into
109
+ `docs/app-profile.md` **with how you know it**, at that moment. Do not promote
110
+ an assumption by quoting that file later.
111
+
112
+ ## Build: objects first, code second
113
+
114
+ Four kinds of thing. Never make the customer learn these words — describe them
115
+ by what they do.
116
+
117
+ | Thing | Answers | Lives in | You write |
118
+ |---|---|---|---|
119
+ | **Action** | *when* should the agent invoke this? | Agent Studio | the contract |
120
+ | **Action handler** | what happens when it does | `agent/actions/<name>.ts` | the code |
121
+ | **Task agent** | *how* to do one reasoning job | Agent Studio | instructions + response schema |
122
+ | **Navigation route** | where the agent can send the user | Agent Studio | a URL template + what the page is for |
123
+
124
+ Knowledge search, navigation and sharing the current tab are platform
125
+ built-ins — no code, and a first experience can lean on them.
126
+
127
+ 1. `create_action` as a **draft**. `generate_action_handler` works from a draft
128
+ schema; do not publish yet. Generating the handler from the schema is what
129
+ keeps the two from drifting.
130
+ 2. Implement the handler from the observed request and response shapes. A
131
+ handler is `fetch` plus `runTask` and nothing more. Import helpers from
132
+ `agent/utils.ts` — inspect that file before writing another `fetch` or rank
133
+ helper.
134
+ 3. Register it in `agent/actions/index.ts` and build (`foldspace build` lints
135
+ first).
136
+
137
+ **Task agents** are for the one LLM subtask deterministic code cannot do —
138
+ extraction, summarisation, classification, normalisation. Not for API calls,
139
+ CRUD, routing or parsing a known shape. Created in Agent Studio, called with
140
+ `runTask({ taskKey, data })` — published, or it does not resolve. `data` carries
141
+ facts, never `prompt` or `instructions`. One owner per concern: extraction
142
+ reads, normalisation converts, nothing does both.
143
+ <https://docs.foldspace.ai/user-guides/task-agents/>
144
+
145
+ **Changing something already live is a production deploy** — immediate, no
146
+ review, no rollback. Never edit it to test. Stand up a `_gen2` beside it, point
147
+ the code at that, and roll back by pointing back. `_gen2`, never `_v2` —
148
+ Foldspace owns the `v` counter. The suffix is permanent.
149
+
150
+ ### The moment a handler exists, prove it
151
+
152
+ Do not batch verification to the end. As soon as a handler is registered:
153
+ build, confirm it reached `dist/index.js`, attach, confirm
154
+ `registration_ok`, execute it against the live endpoint, and check the result
155
+ is **not empty**. An empty widget that renders cleanly is not a working
156
+ experience.
157
+
158
+ Say what that proved and what it did not: executing a handler proves the
159
+ handler; only a real chat turn proves the experience (gate 5). Then record the
160
+ endpoint, auth source, response shape and row count in `docs/app-profile.md`,
161
+ with how you established each.
162
+
163
+ ## Reachability: everything must be published
164
+
165
+ | | Published to run? | Second gate |
166
+ |---|---|---|
167
+ | Action | **yes** | handler registered in `agent/actions/index.ts` |
168
+ | Task agent | **yes** | a `taskKey` in code names it |
169
+ | Navigation route | **yes** | `isActive: true` |
170
+
171
+ Publishing a **new** object is inert; publishing an **edit to a live one** is
172
+ the deploy above. Navigation has **no publish tool on the MCP** and
173
+ `get_navigation_route` returns only `isActive` — publish routes in Agent Studio
174
+ and say so. Route descriptions are a retrieval surface: write what the page is
175
+ *for*, in the user's words, and pair every parameterised route with the lookup
176
+ action that produces its parameter — or ship neither.
177
+
178
+ **Ask before publishing.** It is required only so the copilot can call the
179
+ action, and it is a live product change when the agent has real users.
180
+
181
+ ## Local loop
106
182
 
107
183
  ```bash
108
184
  npm run dev
@@ -110,99 +186,147 @@ npm run inject
110
186
  npx foldspace attach --daemon
111
187
  ```
112
188
 
113
- `inject` launches an isolated Chrome profile and records its debug port.
114
- It does not generate or load an application extension. Observe the customer's
115
- workflow after inject and **before** attach, while chrome-devtools MCP can use
116
- the same Chrome. `attach` prepares the page and loads the local `dist/index.js`
117
- bundle through CDP. Coding agents must use `--daemon` so the invoking tool
118
- returns after `[lifecycle] inspect_registration:…`; foreground `npm run attach`
119
- is for humans watching the terminal. An empty local registry is valid. `npm run
189
+ `inject` launches an isolated Chrome profile and records its debug port; it
190
+ loads nothing. In a first session `attach` comes straight after sign-in (L0);
191
+ to observe afterwards, `attach --stop`, use chrome-devtools MCP against the
192
+ same Chrome, then attach again. `attach` prepares the page and loads the local
193
+ `dist/index.js` over CDP. Coding agents use `--daemon` so the tool returns after
194
+ `[lifecycle] inspect_registration:…`. An empty local registry is valid; `npm run
120
195
  build` is still required so `dist/index.js` exists.
121
196
 
122
- Use the default swap only when the page already has the configured product and
123
- agent. Use `--bootstrap` only when the page has no Foldspace SDK, and
124
- `--replace` when it embeds a different product or agent, or when an SDK is
125
- present without the configured agent.
126
-
127
- The attach log must report `inspect_registration:registration_ok` before
128
- treating the page as registered. Zero captured actions is success when the
129
- local registry is empty. On `registration_mismatch`, read
130
- `npx foldspace help attach --json` diagnostics and map those names onto the
131
- lifecycle details (`missingActionNames`, `unexpectedActionNames`,
132
- `diagnosticError`).
197
+ Attach mode is read off the page, never asked: default swap when the page
198
+ already has the configured product and agent; `--bootstrap` when it has no
199
+ Foldspace SDK; `--replace` when it embeds a different product or agent, or an
200
+ SDK without the configured agent.
133
201
 
134
- While attach is running it owns the debug port. Detach (`attach --stop`, or
135
- Ctrl-C in the foreground) before using chrome-devtools MCP against the same
136
- Chrome.
202
+ `inspect_registration:registration_ok` is the signal. On `registration_mismatch`
203
+ read `npx foldspace help attach --json` and map `missingActionNames`,
204
+ `unexpectedActionNames`, `diagnosticError`. While attach runs it owns the debug
205
+ port — `attach --stop` before using chrome-devtools MCP again.
137
206
 
138
- Prove a named action through the visible agent. After the user talks to the
207
+ Prove a named action through the visible agent: after the human talks to the
139
208
  copilot, read the daemon log for `[actions]` SDK callback and local
140
- execute/render lines; do not invoke the handler directly. Those lines record
141
- names, statuses, durations, and parameter keys only — not results or error
142
- bodies.
143
-
144
- ## Product defaults
145
-
146
- These are not product-specific. Lint can catch some of them in handler code;
147
- Agent Studio copy is on you.
148
-
149
- - **No emoji** in copilot replies, cards, or handler strings. Put that in the
150
- agent's Behavior instructions (MCP cannot write that field, so each new
151
- action's Studio `instructions` must carry the line too).
152
- - **Never send the user out of the host app.** No "open in &lt;product&gt;"
153
- button and no pasted URLs — navigate with a navigation route, same tab.
154
- - **Check row counts before choosing the experience.** On an empty account the
155
- useful first action is one that creates data.
209
+ execute/render lines. Those record names, statuses, durations and parameter keys
210
+ only — never results or error bodies.
156
211
 
157
212
  ## Verification gates
158
213
 
159
214
  Do not report success without all six:
160
215
 
161
- 1. TypeScript compiles with `npm run typecheck` (`tsc --noEmit -p tsconfig.json`).
162
- Do not run `npx tsc` — that can install the wrong package.
163
- 2. `foldspace lint` reports no errors (`foldspace build` runs this first).
164
- 3. The expected handler appears in `dist/index.js`.
165
- 4. The browser reports `inspect_registration:registration_ok`. For a named
166
- action, the captured registry includes that handler.
167
- 5. The action behaves correctly against the real target workflow. Confirm
168
- `[actions] local-handler:execute` in the attach log after the user exercises
169
- the visible agent. For a widget, that log must include `local-handler:render`
170
- from a real chat turn — not a hand-built `render()` call.
171
- 6. Browser evidence came from the live target, not from hand-authored examples.
216
+ 1. `npm run typecheck` compiles (`tsc --noEmit -p tsconfig.json`). Do not run
217
+ `npx tsc` — it can install the wrong package.
218
+ 2. `foldspace lint` reports no errors (`foldspace build` runs it first).
219
+ 3. The handler appears in `dist/index.js`.
220
+ 4. The browser reports `inspect_registration:registration_ok`, and for a named
221
+ action the captured registry includes it.
222
+ 5. The action behaves against the real workflow: `[actions]
223
+ local-handler:execute` in the attach log after the human exercises the
224
+ visible agent. A widget also needs `local-handler:render` from a real chat
225
+ turn — not a hand-built `render()` call. `runAction` refuses render actions
226
+ and driving `render()` yourself never runs `execute()`.
227
+ 6. Browser evidence came from the live target, not hand-authored examples.
172
228
  Neighbour fixtures still pass when they exist.
173
229
 
230
+ ## Rules that are not negotiable
231
+
232
+ 1. **Handlers return data only.** Never `directive`, `instructions`, or a
233
+ paragraph telling the copilot what to say — those live in Agent Studio.
234
+ Never return `ApiResult.detail` from `execute`; it is for the console.
235
+ Validate parameters and return sanitised errors.
236
+ 2. **`render` receives `execute`'s return value**, not the input params. A
237
+ widget that lacks the ids the card needs passes in isolation and fails in the
238
+ real chat. <https://docs.foldspace.ai/guides/in-chat-ui/>
239
+ 3. **Say what you verified and what you assumed.** "The docs say X" and "I
240
+ think X" are different claims — mark which. Search `docs.foldspace.ai` over
241
+ MCP (`search_docs`, `fetch_page`) before asserting how the platform behaves;
242
+ cite the page, or say plainly that the docs do not cover it and write down
243
+ what you learned.
244
+ 4. **Do not send chat messages to test.** Every message is a real conversation
245
+ in the customer's list. `runTask` creates no conversation record — use it.
246
+ 5. **Test mode is off by default.** A new agent has no traffic to pollute, and
247
+ watching real conversations appear is the point of the first run. Arm it
248
+ only when the tenant already has real conversations — and then on **every**
249
+ instance via `armAllInstances`, because `foldspace.agent({apiName})` returns
250
+ the overlay handle, which is not the one serving an embedded chat. Nothing
251
+ can confirm it is on afterwards; never send probe messages to find out.
252
+ 6. **The human is not necessarily a developer.** Never hand over a terminal
253
+ line, a stack trace, or a choice between flags. Name experiences by outcome
254
+ — *"show my plan and credits"*, not `get_account_status`. Their two jobs:
255
+ sign in, and talk to the agent.
256
+ 7. **Be short.** No recap of what was established, no restating the plan before
257
+ each step. Progress is one line: what you are doing, the level reached, and
258
+ the question if there is one.
259
+ 8. **Subagents investigate; they do not build.** A read-only investigation
260
+ that returns a summary belongs in a subagent because it floods context.
261
+ Never delegate a handler or a gate — a summary of a gate is not a gate.
262
+
263
+ ## Product defaults
264
+
265
+ Lint catches some of these in handler code; Agent Studio copy is on you.
266
+
267
+ - **No emoji** in copilot replies, cards or handler strings. Put that in the
268
+ agent's Behavior instructions, and in each new action's Studio `instructions`.
269
+ - **Never send the user out of the host app.** No "open in <product>" button,
270
+ no pasted URLs — navigate with a route, same tab.
271
+
174
272
  ## Layout
175
273
 
176
274
  - `agent/actions/` — one handler per action (`execute`, optional `render`),
177
- registered in `index.ts`
275
+ registered in `index.ts`. The key must equal the action key in Agent Studio;
276
+ a mismatch does not error, the action simply never fires.
178
277
  - `agent/api/` — one HTTP helper per endpoint
179
278
  - `agent/constants.ts` — agent, product, domain, plus empty `API_BASE` /
180
279
  `AUTH_SOURCE` and `LOAD_MODE`
181
- - `agent/utils.ts` — configure the harness runtime and re-export it. **This is
182
- the only import surface for actions.**
183
- - `foldspace.dev.json`local harness target configuration
280
+ - `agent/utils.ts` — configures the harness runtime and re-exports it. **The
281
+ only import surface for actions.**
282
+ - `agent/views/`branded cards
184
283
  - `docs/app-profile.md` — what is known about this app, and how it was established
284
+ - `foldspace.dev.json` — local harness target configuration
185
285
  - `CLAUDE.md` — this file, imported — plus what is specific to this product
186
286
 
187
287
  Do not introduce another bundler or bundle format.
188
288
 
289
+ ## Working examples to copy from
290
+
291
+ Before writing a handler or a card, read
292
+ `node_modules/@foldspace_npm/harness/recipes/INDEX.md` — the whole table, it is
293
+ short — and open the recipe closest to the outcome. Each is a finished
294
+ piece of a build, generalised from production builds with the customer taken
295
+ out: the API helper, the handler or card, and a recorded response per state. Copy its `agent/` files across, then replace the
296
+ placeholder path and fields with what you observed. They match the harness
297
+ version you have installed, and every one is built and run on each release.
298
+
299
+ L0 → `who-is-the-user`. L2 → `find-by-name`, and `pick-from-a-list` when the
300
+ user has to choose. App does not use a bearer token → `swap-the-login-method`.
301
+ Each says how many production builds carry the pattern and what they learned.
302
+ If nothing fits — there is no L1 card recipe yet — say so and build from
303
+ `agent/actions/_example.ts`.
304
+
189
305
  ## What you already have
190
306
 
191
- Import from `../utils`. Inspect that file before implementing another
192
- general-purpose helper. MCP `generate_action_handler` may still emit a
193
- skeleton that does not import it — fix the import when you implement.
307
+ Import from `../utils`. `generate_action_handler` may emit a skeleton that does
308
+ not import it fix the import when you implement.
194
309
 
195
310
  | Helper | Use when | Do not use when |
196
311
  |---|---|---|
197
312
  | `apiFetch` / `apiFetchBinary` | The app's own JSON/file API, after a real 200 | Public marketing hosts (`publicFetch`); custom auth headers |
198
313
  | `publicFetch` | Unauthenticated / marketing origin | Signed-in product APIs |
199
314
  | `getAuthToken` / `parseJwt` | `AUTH_SOURCE` is `localStorage` or `cookie` | Custom header schemes — override `apiFetch` in `utils.ts` |
200
- | `rankBy` | User typed a name; API search is exact or ignored | Domain ranking (invoices, coverage, MasterFormat) |
201
- | `renderLoading` / `Empty` / `Error` / `Fatal` | Chatterblock empty/error paths | Branded cards — those stay in `agent/views/` |
315
+ | `rankBy` | User typed a name; API search is exact or ignored | Domain ranking (invoices, coverage, industry classification codes) |
316
+ | `renderLoading` / `Empty` / `Error` / `Fatal` | Chatterblock states | Branded cards — those stay in `agent/views/` |
317
+ | `renderFailure` | Any `{ ok: false }` from `apiFetch` — it picks the state from `reason` | You need bespoke copy for one reason; then branch on `reason` yourself |
318
+ | `httpFailure` / `networkFailure` | Your own `apiFetch` (custom header, cookies) — keeps the same `reason`s | The default `apiFetch` fits; then you never call these |
202
319
  | `getAgent` | Talking to Foldspace | You need a specific instance — then `agentIds()` |
203
- | `armAllInstances` | Attach/bootstrap setup | Inside `execute` (can ship into `dist`; attach already arms test mode) |
320
+ | `armAllInstances` | Attach/bootstrap setup | Inside `execute` |
204
321
  | `redact` / `mapWithConcurrency` | Logging tokens; batching fetches | — |
205
322
 
323
+ Every failed call carries a `reason` — `signed_out`, `forbidden`, `not_found`,
324
+ `rate_limited`, `timeout`, `unreachable`, `config`, `server`. Branch on that,
325
+ not on `status`, and do not re-detect it. The session you observed while
326
+ building is not the session at run time: **expect `signed_out`** and let
327
+ `renderFailure` show it. `not_found` and zero rows are both `renderEmpty` with
328
+ the query named — never an error, never a blank card.
329
+
206
330
  If the observed auth is not Bearer + `localStorage`/`cookie`, stop re-exporting
207
331
  that one function and keep the rest:
208
332
 
@@ -211,25 +335,10 @@ export { getAgent, rankBy, renderEmpty } from "@foldspace_npm/harness/runtime";
211
335
  export { apiFetch } from "./api";
212
336
  ```
213
337
 
214
- ## Agent learnings
215
-
216
- `docs/app-profile.md` is the durable record of this app. Fill it as you probe,
217
- not afterwards. Other notes in `docs/` are fine for session-specific lessons.
218
-
219
- - Before similar work, read `docs/app-profile.md` and any other notes in `docs/`.
220
- - After non-obvious discoveries, add them to the profile (or a short extra
221
- note) covering verified gotchas, failed approaches, and how they were
222
- established.
223
- - Keep notes concise and evidence-based.
224
- - Do not store secrets, cookies, HAR files, browser storage, or other transient
225
- session data in `docs/`.
226
-
227
338
  ## Safety
228
339
 
229
340
  - Do not commit secrets, cookies, HAR files, browser storage, or
230
- `.foldspace-dev/`.
341
+ `.foldspace-dev/`. Nothing transient goes in `docs/`.
231
342
  - Local handler changes are not cloud publication.
232
343
  - Creating or publishing Foldspace resources requires explicit approval.
233
344
  - Action keys and parameter names must match the published schema.
234
- - Search Foldspace documentation before asserting unfamiliar platform
235
- behaviour.
package/README.md CHANGED
@@ -26,7 +26,7 @@ constraint this package exists to protect.
26
26
  ### What is genuinely shared
27
27
 
28
28
  - `agent/actions/*` and `agent/api/*` — a handler is `fetch` plus `runTask`,
29
- nothing more. The actions built for Figma run unmodified in either track.
29
+ nothing more. Actions built in one track run unmodified in the other.
30
30
  - `agent/utils.ts` — configures and re-exports `@foldspace_npm/harness/runtime`
31
31
  - `foldspace build` — esbuild → `dist/index.js`
32
32
  - `foldspace deploy` — publish to `agent/actions/<env>/<productId>/<agentApiName>`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foldspace_npm/harness",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "Build and verify portable Foldspace action artifacts against a live app.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -21,6 +21,7 @@
21
21
  "bin",
22
22
  "src",
23
23
  "templates",
24
+ "recipes",
24
25
  "README.md",
25
26
  "CLAUDE.md"
26
27
  ],
@@ -0,0 +1,15 @@
1
+ # Recipe index
2
+
3
+ Read this whole table, then open the recipe closest to the outcome.
4
+
5
+ | Recipe | Level | What it gives | Kind | Proven by |
6
+ |---|---|---|---|---|
7
+ | [`who-is-the-user`](who-is-the-user/) | **L0** | Foldspace knows who is signed in — id, email, name, role, subscription — so conversations and analytics are not anonymous | Runs once at start-up. No action, no card | 4 production builds |
8
+ | [`find-by-name`](find-by-name/) | **L2** | The agent turns a name the user said into the id the next action needs; a count when no name is given; every tie when the name is ambiguous; near-misses when nothing matches | A data action. **No card** | 3 production builds |
9
+ | [`pick-from-a-list`](pick-from-a-list/) | **L2** | A clickable list, shown **only** when the user has to choose — with "none of these" and cancel | An action with a card that waits for the user | 1 production build |
10
+ | [`swap-the-login-method`](swap-the-login-method/) | any | `apiFetch` for an app that authenticates with a custom header, or with cookies, instead of a bearer token | A replacement `agent/utils.ts` | 2 production builds (3 builds, 3 different schemes) |
11
+
12
+ **Not here yet — no production build proves it:** the user's own plan or
13
+ settings drawn as a card (L1). In production that data is sent to `identify`
14
+ (`who-is-the-user`), not rendered. Navigation (L3), writes (L4), uploads and
15
+ task agents are also still to come.
@@ -0,0 +1,46 @@
1
+ # Recipes
2
+
3
+ Finished pieces of a build to **read and adapt** — each one generalised from a
4
+ pattern that is live in production builds, with the customer taken out.
5
+ `_example.ts` is an empty skeleton; these are complete.
6
+
7
+ They ship inside the harness on purpose: a recipe always matches the runtime it
8
+ imports, and every one is installed into a freshly scaffolded project, built,
9
+ typechecked and run against its fixtures on every harness change
10
+ (`test/recipes.test.mjs`). A recipe that stops working fails the release instead
11
+ of a customer session.
12
+
13
+ ## Use one
14
+
15
+ 1. Read `INDEX.md` and pick the closest outcome.
16
+ 2. Copy its `agent/` files into your project's `agent/` — the layout is the
17
+ same, so the imports already resolve.
18
+ 3. Replace every `__observe_me` placeholder — paths, storage keys, claim names —
19
+ with what you **observed** in the live app, and record how you know in
20
+ `docs/app-profile.md`. The placeholders are deliberate: none can succeed.
21
+ 4. Register actions in `agent/actions/index.ts` under the **same key** as in
22
+ Agent Studio, then run the six gates.
23
+
24
+ If nothing here fits, say so and build from `_example.ts`.
25
+
26
+ ## Where they come from
27
+
28
+ Each recipe's README says how many production builds carry the pattern and what
29
+ those builds learned the hard way. **What is taken is the shape** — the order of
30
+ calls, what is returned to the agent, where the traps are. **What is not taken**
31
+ is any customer's name, endpoint, identifier, styling or code. Contracts are the
32
+ documented ones: `execute(params)` and
33
+ `render(data, host, header, callback, cancel)`
34
+ (<https://docs.foldspace.ai/guides/in-chat-ui/>), `foldspace.identify(context)`
35
+ (<https://docs.foldspace.ai/start/user-context/>), and the harness runtime.
36
+
37
+ Error handling is **not** taken from those builds. They predate `reason`: one
38
+ turns every failure into "unexpected error", another reports a missing session
39
+ as "no projects found". Recipes use the runtime's failure reasons instead.
40
+
41
+ ## What every recipe demonstrates
42
+
43
+ - `execute` returns **data and facts** — never `detail`, never instructions.
44
+ - A missing or expired session is `signed_out`, not an empty result.
45
+ - DOM is built with `textContent`, never `innerHTML` with API data, and styles
46
+ go in `header` — `host` lives inside the component's frame.