@foldspace_npm/harness 0.1.27 → 0.1.29

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 (119) hide show
  1. package/CLAUDE.md +301 -188
  2. package/README.md +54 -36
  3. package/bin/ask.mjs +9 -7
  4. package/bin/attach.mjs +80 -51
  5. package/bin/badge.mjs +28 -8
  6. package/bin/build.ts +19 -6
  7. package/bin/cli.mjs +23 -6
  8. package/bin/inject.mjs +3 -4
  9. package/bin/observe.mjs +22 -6
  10. package/bin/published.mjs +1 -1
  11. package/package.json +6 -2
  12. package/recipes/INDEX.md +13 -8
  13. package/recipes/README.md +30 -7
  14. package/recipes/account-overview/README.md +6 -6
  15. package/recipes/account-overview/agent/actions/show_account_overview.ts +11 -16
  16. package/recipes/account-overview/agent/views/brand.ts +23 -14
  17. package/recipes/account-overview/agent/views/overview.ts +13 -17
  18. package/recipes/bottom-bar/README.md +12 -9
  19. package/recipes/bottom-bar/agent/bottomBar.ts +14 -12
  20. package/recipes/create-a-record/README.md +97 -0
  21. package/recipes/create-a-record/agent/actions/create_project.ts +26 -0
  22. package/recipes/create-a-record/agent/api/projects.ts +62 -0
  23. package/recipes/create-a-record/agent/projects.ts +145 -0
  24. package/recipes/create-a-record/agent/views/approval.ts +181 -0
  25. package/recipes/create-a-record/agent/views/brand.ts +35 -0
  26. package/recipes/create-a-record/agent/views/fields.ts +252 -0
  27. package/recipes/create-a-record/agent/views/project-card.ts +253 -0
  28. package/recipes/create-a-record/agent/views/steps.ts +92 -0
  29. package/recipes/create-a-record/fixtures/owners.ok.json +9 -0
  30. package/recipes/create-a-record/fixtures/project.created.json +1 -0
  31. package/recipes/create-a-record/fixtures/project.rejected.json +1 -0
  32. package/recipes/create-a-record/recipe.json +13 -0
  33. package/recipes/create-a-record/variants/actions/create_project.steps.ts +160 -0
  34. package/recipes/find-by-name/README.md +3 -1
  35. package/recipes/find-by-name/agent/actions/find_project_id.ts +1 -1
  36. package/recipes/lookup-then-view/README.md +73 -0
  37. package/recipes/lookup-then-view/agent/accounts.ts +174 -0
  38. package/recipes/lookup-then-view/agent/actions/find_account.ts +108 -0
  39. package/recipes/lookup-then-view/agent/actions/show_account_overview.ts +42 -0
  40. package/recipes/lookup-then-view/agent/api/accounts.ts +68 -0
  41. package/recipes/lookup-then-view/agent/views/account.ts +183 -0
  42. package/recipes/lookup-then-view/agent/views/brand.ts +35 -0
  43. package/recipes/lookup-then-view/agent/views/format.ts +73 -0
  44. package/recipes/lookup-then-view/agent/views/pick-one.ts +240 -0
  45. package/recipes/lookup-then-view/fixtures/accounts.json +319 -0
  46. package/recipes/lookup-then-view/fixtures/overview.ok.json +122 -0
  47. package/recipes/lookup-then-view/fixtures/overview.unsigned.json +14 -0
  48. package/recipes/lookup-then-view/recipe.json +11 -0
  49. package/recipes/lookup-then-view/studio/find_account.json +10 -0
  50. package/recipes/lookup-then-view/studio/show_account_overview.json +15 -0
  51. package/recipes/opportunities-at-risk/README.md +5 -5
  52. package/recipes/opportunities-at-risk/agent/actions/show_opportunities_at_risk.ts +9 -7
  53. package/recipes/opportunities-at-risk/agent/opportunities.ts +11 -10
  54. package/recipes/opportunities-at-risk/agent/views/at-risk.ts +1 -1
  55. package/recipes/opportunities-at-risk/agent/views/brand.ts +26 -15
  56. package/recipes/pick-from-a-list/README.md +7 -6
  57. package/recipes/pick-from-a-list/agent/actions/choose_project.ts +27 -12
  58. package/recipes/pick-from-a-list/agent/views/brand.ts +30 -9
  59. package/recipes/pick-from-a-list/agent/views/picker.ts +3 -3
  60. package/recipes/prepare-for-a-meeting/README.md +4 -4
  61. package/recipes/prepare-for-a-meeting/agent/actions/prepare_for_meeting.ts +3 -3
  62. package/recipes/records-list/README.md +6 -6
  63. package/recipes/records-list/agent/accounts.ts +3 -1
  64. package/recipes/records-list/agent/actions/list_accounts.ts +7 -7
  65. package/recipes/records-list/agent/views/brand.ts +26 -14
  66. package/recipes/server-rendered-forms/README.md +35 -0
  67. package/recipes/server-rendered-forms/agent/actions/create_record_via_form.ts +62 -0
  68. package/recipes/server-rendered-forms/agent/api/pages.ts +57 -0
  69. package/recipes/server-rendered-forms/agent/forms.ts +192 -0
  70. package/recipes/server-rendered-forms/fixtures/form.errors.html +9 -0
  71. package/recipes/server-rendered-forms/fixtures/form.html +17 -0
  72. package/recipes/server-rendered-forms/fixtures/list.html +9 -0
  73. package/recipes/server-rendered-forms/fixtures/record.html +4 -0
  74. package/recipes/server-rendered-forms/recipe.json +10 -0
  75. package/recipes/several-hosts/README.md +10 -13
  76. package/recipes/several-hosts/agent/hosts.ts +19 -136
  77. package/recipes/several-hosts/fixtures/wrong-page.json +6 -0
  78. package/recipes/several-hosts/recipe.json +3 -3
  79. package/recipes/swap-the-login-method/README.md +16 -5
  80. package/recipes/swap-the-login-method/agent/utils.ts +52 -3
  81. package/recipes/swap-the-login-method/variants/utils.cookies.ts +59 -6
  82. package/recipes/update-meeting-notes/README.md +10 -4
  83. package/recipes/update-meeting-notes/agent/actions/update_meeting_notes.ts +16 -5
  84. package/recipes/upload-contacts/README.md +5 -5
  85. package/recipes/upload-contacts/agent/actions/upload_contacts.ts +7 -7
  86. package/recipes/upload-contacts/agent/views/brand.ts +24 -14
  87. package/recipes/upload-contacts/agent/views/uploader.ts +28 -17
  88. package/recipes/who-is-the-user/README.md +7 -3
  89. package/src/ask-core.mjs +3 -3
  90. package/src/attach-daemon.mjs +9 -2
  91. package/src/attach-helpers.mjs +105 -0
  92. package/src/attach-preflight.mjs +34 -11
  93. package/src/badge-core.mjs +1 -1
  94. package/src/cdp-client.mjs +14 -19
  95. package/src/cdp-ownership.mjs +41 -20
  96. package/src/cli-help.mjs +7 -5
  97. package/src/cli-registry.mjs +65 -46
  98. package/src/diagnostics.mjs +1 -1
  99. package/src/init.mjs +5 -3
  100. package/src/lint/index.mjs +10 -1
  101. package/src/lint/rules.mjs +371 -35
  102. package/src/observe-core.mjs +163 -34
  103. package/src/region.mjs +1 -1
  104. package/src/runtime/agent.ts +16 -12
  105. package/src/runtime/http.ts +25 -15
  106. package/src/runtime/match.ts +1 -1
  107. package/src/runtime/render.ts +4 -4
  108. package/src/transports/README.md +2 -2
  109. package/src/upgrade.mjs +33 -10
  110. package/templates/agent-starter/CLAUDE.md +8 -5
  111. package/templates/agent-starter/README.md +32 -33
  112. package/templates/agent-starter/agent/actions/_example.ts +4 -4
  113. package/templates/agent-starter/agent/actions/index.ts +2 -2
  114. package/templates/agent-starter/agent/constants.ts +4 -3
  115. package/templates/agent-starter/agent/utils.ts +15 -18
  116. package/templates/agent-starter/claude-settings.json +1 -1
  117. package/templates/agent-starter/docs/app-profile.md +19 -14
  118. package/templates/agent-starter/foldspace.dev.json +1 -1
  119. package/recipes/several-hosts/fixtures/nav-record.json +0 -5
package/CLAUDE.md CHANGED
@@ -4,23 +4,24 @@ You are adding an AI agent to a product that already exists. It answers
4
4
  questions and *does things* inside that product, with the user's own session.
5
5
 
6
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".
7
+ commands; they sign in, talk to the agent, and say yes or no.** Their first
8
+ session ends at something they can **see on their own page** - not at
9
+ "it builds".
9
10
 
10
11
  ## Every session starts the same way
11
12
 
12
13
  1. **Harness version.** Run `npx foldspace help --json` (from an older install,
13
14
  `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.
17
- `build` and `attach` check too: behind prints one line and carries on; a
18
- version Foldspace **withdrew** refuses to run and says why. Tell the user
19
- the reason and ask to upgrade. Only if they decide not to, rerun with
20
- `FOLDSPACE_SKIP_UPDATE_CHECK=1` - never set it on your own. If the
21
- project `CLAUDE.md` does not contain
15
+ `update.outdated` is true, say which version the project is on and which is
16
+ newest, and **ask before upgrading**; then `npx foldspace upgrade --yes` and
17
+ `npm run build`. Never upgrade as a side effect of build, attach or
18
+ implementing an action. `build` and `attach` check too: on an older version
19
+ they print one line and carry on; on a version Foldspace **withdrew** they
20
+ refuse to run and say why. Tell the human the reason and ask to upgrade.
21
+ Only if they decide not to, rerun with `FOLDSPACE_SKIP_UPDATE_CHECK=1` -
22
+ never set it on your own. If the project `CLAUDE.md` does not contain
22
23
  `@node_modules/@foldspace_npm/harness/CLAUDE.md`, ask before
23
- `foldspace upgrade --refresh-instructions`.
24
+ `npx foldspace upgrade --refresh-instructions`.
24
25
  2. **Is Foldspace connected? Check — never assume.** `region` in
25
26
  `foldspace.dev.json` (absent means `us`) picks the server: `foldspace`, or
26
27
  `foldspace-eu` for `eu`. Use that server's tools all session. Call its
@@ -28,8 +29,8 @@ something they can **see on their own page** — not at "it builds".
28
29
 
29
30
  | Result | Do |
30
31
  |---|---|
31
- | The tool does not exist | The Foldspace MCP is not installed in this editor. Give the one install step for *this* region and editor — **EU**, any editor: the MCP block on <https://foldspace.ai/eu/agents.md>, never the plugin. **US** — Claude Code: `claude plugin install foldspace-agent-builder@foldspace-plugins`; anything else: the MCP block on <https://foldspace.ai/agents.md>. Then check again |
32
- | 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-eu** (**EU**) or **foldspace** (**US**) → finish in the browser — then check again |
32
+ | The tool does not exist | The Foldspace MCP is not installed in this editor. Install it yourself, the one way that fits *this* region and editor. **EU** - any editor: the MCP block on <https://foldspace.ai/eu/agents.md>, never the plugin. **US** - Claude Code with git on the machine (`command -v git`): `claude plugin marketplace add https://github.com/foldspace-ai/plugins` (already added, or added from another source, is fine), then `claude plugin install foldspace-agent-builder@foldspace-plugins`; without git, or any other editor: the MCP block on <https://foldspace.ai/agents.md>. Either way it needs sign-in: start it before your turn ends, as **Connect Foldspace tools** on that page says. **The plugin** loads when your turn ends: call `list_agents` again on the human's next message. **A server added from the MCP block** (`claude mcp add`) never reaches a chat that is already running - not on the next message, not through `/mcp`: once it is signed in, the human opens a new chat in this folder and repeats their request. `claude mcp get` saying Connected proves only the setting; the check is `list_agents` in the chat you are in |
33
+ | 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-eu** (**EU**) or **foldspace** (**US**; listed as `plugin:foldspace-agent-builder:foldspace` when the plugin installed it) → finish in the browser - then check again. If `/mcp` does not list that server, it was added after this chat started: the human opens a new chat in this folder, as above |
33
34
  | 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 |
34
35
 
35
36
  3. **Orient** — `get_agent_settings`, `list_actions`, `list_task_agents`,
@@ -80,7 +81,7 @@ current CLI contract — risk, prerequisites, effects, next step.
80
81
  | Level | Reached when |
81
82
  |---|---|
82
83
  | **L0 · agent live** | injected, visible on the page |
83
- | **L1 · read settings** | the user's own account, plan or settings in a widget, showing their real values |
84
+ | **L1 · read settings** | the user's own account, plan or settings on a card, showing their real values |
84
85
  | **L2 · look up entities** | a search from the user's own words, showing rows |
85
86
 
86
87
  1. **Get the agent live first (L0) - with no actions.** Straight after the
@@ -103,33 +104,37 @@ current CLI contract — risk, prerequisites, effects, next step.
103
104
  2. **Then offer experiences - from what you already have.** With the agent on
104
105
  their screen, offer three or four read-only experiences as a choice,
105
106
  recommended first. Sources: the product's own menu (`npx foldspace observe
106
- menu` takes a second and moves nothing), the conversation starters seeded at sign-up,
107
- `discover_actions`, and `docs/app-profile.md`. `discover_actions` gives
108
- candidates, not an inventory. Do not investigate the app to build this
107
+ menu` takes a second and moves nothing), the conversation starters seeded
108
+ at sign-up, `discover_actions`, and `docs/app-profile.md`. `discover_actions`
109
+ gives candidates, not an inventory. Do not investigate the app to build this
109
110
  list. Order them cheapest first: the user's own data with no parameter
110
111
  (L1) before a search with one parameter (L2).
111
112
  3. **Only then observe - and only what the chosen experience needs** (**Find
112
113
  the call yourself**). Check the row count as part of that: an experience
113
114
  on a resource the account has zero of is a demo of an empty state, so say
114
- so and offer the next one. On an empty account the useful first action is
115
- one that creates data - say so instead.
116
-
117
- **Say when the test window is yours, and when it is theirs - on the page and
118
- in chat.** You and the human share one Chrome window. `observe` and `ask`
119
- raise the working pill by themselves for as long as they run, and drop it
120
- after. For the longer stretch - from the moment you start working on their
121
- window until it is their turn - run `npx foldspace badge working` (the pill at
122
- the foot of the window says Foldspace is building here, leave it alone) and say
123
- so in chat: *"I'm going to work in that Chrome window for a few minutes -
124
- please leave it alone until I tell you it's ready."* When it is their turn,
125
- `npx foldspace badge ready` takes the pill away (the small corner tag comes
126
- back) and in chat you say what to try. No bar for their turn: they may type
127
- whatever, whenever; the pill only ever asks them to wait. `foldspace badge label` puts the small
128
- corner tag back; `off` removes it, for a screenshot they will share.
129
-
130
- **After they have tried it, offer the second wave - one choice, recommended
131
- first, not four questions.** Once you have handed over (`badge ready`) and the
132
- human has asked the agent the question:
115
+ so and offer the next one. When the account has nothing to read at all,
116
+ say so and offer a write first: a card that creates a record and writes
117
+ only when the user approves it (`recipes/create-a-record`).
118
+
119
+ **Say when the test window is yours, and when it is theirs - on the page and in
120
+ chat.** You and the human share one Chrome window. `ask` and `observe` (except
121
+ `screenshot`, `pages` and `wait-login`) raise the working pill by themselves for
122
+ as long as they run, and drop it after. For the longer stretch - from the moment
123
+ you start working on their window until it is their turn - run
124
+ `npx foldspace badge working` (the pill at the foot of the window says Foldspace
125
+ is building here, leave it alone) and say so in chat: *"I'm going to work in
126
+ that Chrome window for a few minutes - please leave it alone until I tell you
127
+ it's ready."* When it is their turn, `npx foldspace badge ready` takes the pill
128
+ away (the small corner tag comes back) and in chat you say what to try. Nothing
129
+ marks their turn: they may type whatever, whenever; the pill only ever asks them
130
+ to wait. `npx foldspace badge label` puts the small corner tag back;
131
+ `npx foldspace badge off` removes it, for a screenshot they will share.
132
+
133
+ **After they have tried it, the second wave: tell Foldspace who the user is
134
+ (item 3 - no question, one line in chat), and offer the rest as one choice,
135
+ recommended first.** Once you have handed over (`badge ready`), the human has
136
+ asked the agent the question and - with a code project - you have taken the
137
+ steps in **Done, and what you say at the end**:
133
138
 
134
139
  1. **Starters.** The sign-up starters point at actions the agent does not have
135
140
  (clicking one answers "that action isn't available"): replace them with the
@@ -140,52 +145,70 @@ human has asked the agent the question:
140
145
  project's start-up code; nothing in their source changes.
141
146
  3. **Tell Foldspace who the user is** - `recipes/who-is-the-user`: from then on
142
147
  their conversations and analytics stop being anonymous, and the agent has
143
- real-time context about who it is talking to. One line in chat, no question.
144
- 4. **A second experience** - and, once a card shows records, navigation to a
145
- record's page.
148
+ real-time context about who it is talking to.
149
+ 4. **A second experience** - a read, or a write through
150
+ `recipes/create-a-record` - and, once a card shows records, navigation to
151
+ a record's page.
152
+ 5. **No emoji in any of the agent's replies** - one line in the agent's
153
+ Behavior instructions (Agent Studio → **Style & Behavior** → **Behavior &
154
+ Responses**). No Foldspace tool writes those, so it is theirs to add: on a
155
+ yes, give them the line to paste and where it goes.
146
156
 
147
157
  Not before the first experience: nothing about it needs any of these, and it
148
158
  is silent work before the human has seen anything.
149
159
 
150
- Uploads, extraction and anything that writes are a later session.
160
+ Uploads and extraction are a later session.
151
161
 
152
162
  ### Ask as a choice, and ask almost nothing
153
163
 
154
164
  Every question to the human is an **enumerated choice with the recommended
155
- option first** (in Claude Code, `AskUserQuestion`). A first session has four
156
- decision points: which agent (only if several), the app's hostname, which
157
- experience first, and whether to publish. The hostname is the only free-text
158
- question. Everything else — attach mode, folder, name, level — you **state and
159
- move on**.
160
-
161
- Then agree the experience in the customer's words, before creating anything:
165
+ option first** (in Claude Code, `AskUserQuestion`). A first session asks only
166
+ this: which agent (only if several), the app's hostname, which experience
167
+ first - and a yes wherever this manual asks for one, such as agreeing the
168
+ experience (below), upgrading, publishing an action, task agent or
169
+ navigation, publishing their code for their users, replacing their starters or
170
+ adding hosts. The hostname is the only free-text question. Everything else -
171
+ attach mode, folder, name, level - you **state and move on**.
172
+
173
+ Then agree the experience in the human's words, before creating anything:
162
174
  *"Show my plan and credits needs one new action on your agent and one card to
163
175
  show it in. Sound right?"*
164
176
 
165
177
  ## One product, several hosts
166
178
 
167
- Many products live on more than one host - `www.`, `analyze.`, `admin.` under
168
- one domain. `init` covers only the host it was given, so on the others the
169
- agent does not appear and every command refuses the window.
179
+ Many products live on more than one host - `www.`, `app.`, `reports.`,
180
+ `admin.` under one domain. `init` covers the host it was given and that
181
+ host's subdomains, so on sibling hosts the agent does not appear and every
182
+ command refuses the window.
170
183
 
171
184
  - **Find out at the start.** Straight after the agent is live, `npx foldspace
172
185
  observe menu` lists the hosts the page links to that belong to the same
173
186
  product and that this project does not cover (`otherHosts`). `observe pages`
174
187
  and the attach log say the same when the window lands on one.
175
188
  - **Widening is the human's yes.** Name the hosts you found and ask once:
176
- *"Your product also lives on analyze.… and communicate.…. Add them so the
177
- agent works there too?"* Then add the one pattern (`"*.example.com"`) to
178
- `hosts` in `foldspace.dev.json`, `npm run inject`, and attach again.
189
+ *"Your product also lives on reports.… and admin.…. Add them so the agent
190
+ works there too?"* Then add the one pattern (`"*.example.com"`) to
191
+ `hosts` in `foldspace.dev.json`, then `npx foldspace attach --stop`,
192
+ `npm run inject`, and attach again with the same flags (**Local loop**).
179
193
  - **Then go there yourself.** `npx foldspace observe screen --goto
180
- https://analyze.example.com/reports` opens a screen on any host the project
194
+ https://reports.example.com/` opens a screen on any host the project
181
195
  names. Do not ask the human to click a tile you can open.
182
- - **An action reads only the host it runs on** - that is the browser. Build
183
- each experience on the host its data lives on, and use
196
+ - **An action reads only what the page it runs on may read** - that is the
197
+ browser: another host's signed-in data is refused unless that host allows
198
+ this one. Build each experience on the host its data lives on, and use
184
199
  `recipes/several-hosts`: an action asked from the wrong host answers
185
200
  `wrong_page` instead of failing.
186
- - **The user and their conversations carry across hosts; the agent carrying
187
- on after it navigated does not** (PLG-5962). `recipes/several-hosts` has the
188
- workaround. It is a workaround: register it (**Rules**, 9).
201
+ - **The user, their conversations and the agent's turn all carry across
202
+ hosts.** The SDK keeps its resume note for 60 seconds in a cookie on the
203
+ domain the hosts share (`example.com`), so an agent that moved the user to
204
+ another host in the same tab carries on there. Only the navigation's own
205
+ result goes with it: another action that ran in the same turn reaches the
206
+ agent as "not available". Build nothing for the hand-over:
207
+ `recipes/several-hosts` is only for the wrong-host answer above. If the
208
+ project already has `installResumeAcrossHosts` (a row under
209
+ **Workarounds** in `docs/app-profile.md`), remove it as that recipe says:
210
+ with both, the user sees an extra message and the agent takes a second
211
+ turn.
189
212
 
190
213
  ## Find the call yourself
191
214
 
@@ -210,16 +233,16 @@ and asking are not the only options — **observing is, and it comes first**:
210
233
  npx foldspace observe screen --click "<a label from menu>" --match "<three to five words for the data>"
211
234
  npx foldspace observe read "<the GET path screen returned>" # GET only: status, field names and types, row count
212
235
  npx foldspace observe auth # only if a read came back 401 or 403
213
- npx foldspace observe styles # their font and colours, for the card
236
+ npx foldspace observe styles --write # agent/views/brand.ts for the cards, before the first card
214
237
  ```
215
238
 
216
239
  `screen` reaches a screen the way a person does and returns at most eight
217
- requests, ranked by your words - field names, value types and row counts,
218
- **never values**. `read` confirms the one you picked. `auth` reports *where*
219
- each header value comes from, never the value: do not go hunting through
220
- storage, cookies or bundles yourself. One experience needs one screen; two
221
- `screen` calls is normal, five means the experience is wrong for this
222
- product - offer the next one.
240
+ requests, ranked by your words - field names and row counts, **never
241
+ values**. `read` confirms the one you picked, with value types. `auth`
242
+ reports *where* each header value comes from, never the value: do not go
243
+ hunting through storage, cookies or bundles yourself. One experience needs
244
+ one screen; two `screen` calls is normal, five means the experience is wrong
245
+ for this product - offer the next one.
223
246
 
224
247
  **`observe` refuses what is not provably looking**, and a refusal is an
225
248
  answer (**Safety**): it follows links, tabs and menu openers only - a plain
@@ -231,7 +254,8 @@ and asking are not the only options — **observing is, and it comes first**:
231
254
  an MFA step, a paywall, a screen that needs a record the account does not
232
255
  have, or anything that would write.
233
256
 
234
- Signing in is the human's step, because `inject` launches a fresh profile.
257
+ Signing in is the human's step, because `inject` launches its own Chrome
258
+ profile (empty the first time; it keeps the sign-in after that).
235
259
  Navigating is yours. **While you drive a signed-in account you are read-only:**
236
260
  GET only, never submit a form, never click anything that creates, sends or
237
261
  deletes.
@@ -243,11 +267,11 @@ an assumption by quoting that file later.
243
267
 
244
268
  ## Build: objects first, code second
245
269
 
246
- Four kinds of thing. With the customer, use **Foldspace's own words** for what
270
+ Four kinds of thing. With the human, use **Foldspace's own words** for what
247
271
  they will see in Agent Studio - **action**, **task agent**, **navigation**,
248
272
  **publish** - and say what each does the first time you use it. Never invent a
249
273
  substitute: no "skill", "ability", "capability" or "tool", and no "switch on"
250
- for publish. "Skill" already means something else in Claude, and the customer
274
+ for publish. "Skill" already means something else in Claude, and the human
251
275
  has seen "Actions" at sign-up and in Agent Studio - a new word breaks the link
252
276
  to what they know. The action handler is yours: call it "the code behind it".
253
277
 
@@ -263,7 +287,15 @@ built-ins — no code, and a first experience can lean on them.
263
287
 
264
288
  1. `create_action` as a **draft**. `generate_action_handler` works from a draft
265
289
  schema; do not publish yet. Generating the handler from the schema is what
266
- keeps the two from drifting.
290
+ keeps the two from drifting. **Declare every parameter fully**: type,
291
+ `required`, and a description that says where the value comes from. That
292
+ schema is what the agent reads to wire actions together by itself - a
293
+ required `accountId` described as "get it from `find_account`" makes it
294
+ call `find_account` first. Showing the one match without asking, and a
295
+ searchable list when there are several or none, come from `find_account`'s
296
+ instructions and code (`recipes/lookup-then-view`). An id marked optional,
297
+ or described only as "the id", leaves the agent guessing, or asking the end
298
+ user for an id they do not have.
267
299
  2. Implement the handler from the observed request and response shapes. A
268
300
  handler is `fetch` plus `runTask` and nothing more. Import helpers from
269
301
  `agent/utils.ts` — inspect that file before writing another `fetch` or rank
@@ -273,29 +305,35 @@ built-ins — no code, and a first experience can lean on them.
273
305
 
274
306
  **Task agents** are for the one LLM subtask deterministic code cannot do —
275
307
  extraction, summarisation, classification, normalisation. Not for API calls,
276
- CRUD, routing or parsing a known shape. Created in Agent Studio, called with
277
- `runTask({ taskKey, data })` — published, or it does not resolve. `data` carries
278
- facts, never `prompt` or `instructions`. One owner per concern: extraction
279
- reads, normalisation converts, nothing does both.
308
+ CRUD, routing or parsing a known shape. Create one with `create_task_agent` and
309
+ publish it with `publish_task_agent` (with the human's yes - it changes their
310
+ account); call it with `getAgent().runTask({ taskKey, data })` - published, or
311
+ it does not resolve. `data` carries facts, never `prompt` or `instructions`. One
312
+ owner per concern: extraction reads, normalisation converts, nothing does both.
280
313
  <https://docs.foldspace.ai/user-guides/task-agents/>
281
314
 
282
- **Changing something already live is a production deploy** — immediate, no
283
- review, no rollback. Never edit it to test. Stand up a `_gen2` beside it, point
284
- the code at that, and roll back by pointing back. `_gen2`, never `_v2` —
285
- Foldspace owns the `v` counter. The suffix is permanent.
315
+ **Publishing an edit to something already live changes the live agent at
316
+ once** - unreviewed, and publishing the earlier version again puts back its
317
+ schema but not the code you changed to match the new one. Never edit it to
318
+ test. Stand up a `_gen2` beside it, point the code at that, and roll back by
319
+ pointing back. `_gen2`, never `_v2` - Foldspace owns the `v` counter. The
320
+ suffix is permanent.
286
321
 
287
322
  ### The moment a handler exists, prove it
288
323
 
289
324
  Do not batch verification to the end. As soon as a handler is registered:
290
- build, confirm it reached `dist/index.js`, attach, confirm
291
- `registration_ok`, execute it against the live endpoint, and check the result
292
- is **not empty**. An empty widget that renders cleanly is not a working
293
- experience.
294
-
295
- Say what that proved and what it did not: executing a handler proves the
296
- handler; only a real chat turn proves the experience (gate 5). Then record the
297
- endpoint, auth source, response shape and row count in `docs/app-profile.md`,
298
- with how you established each.
325
+ build, confirm it reached `dist/index.js`, `attach --stop` and attach again
326
+ with the same flags (**Local loop**), confirm `registration_ok`, and
327
+ `npx foldspace observe read` the endpoint it calls. Once the action is
328
+ published (**Ask before publishing**),
329
+ `npx foldspace ask "<the question>" --retry 2` and check that the reply - and
330
+ the card, when the action draws one - shows real data: an empty answer only
331
+ proves the call ran.
332
+
333
+ Say what that proved and what it did not: `observe read` proves the endpoint
334
+ answers for this account; only the chat turn proves the experience (gate 5).
335
+ Then record the endpoint, auth source, response shape and row count in
336
+ `docs/app-profile.md`, with how you established each.
299
337
 
300
338
  ## Reachability: everything must be published
301
339
 
@@ -305,53 +343,71 @@ with how you established each.
305
343
  | Task agent | **yes** | a `taskKey` in code names it |
306
344
  | Navigation route | **yes** | `isActive: true` |
307
345
 
308
- Publishing a **new** object is inert; publishing an **edit to a live one** is
309
- the deploy above. **Navigation has two switches, and both must be on.** Each
310
- route has its own `isActive`, set when you create it (`create_navigation_route`
311
- / `bulk_create_navigation_routes`, `isActive: true`). The agent has one
312
- agent-level switch, **off by default on a new subscription**: `publish_navigation`
313
- turns it on, `unpublish_navigation` off, and neither touches the routes' own
314
- `isActive`. `list_navigation_routes` shows both. There is no MCP tool to flip a
315
- single route's `isActive` after creation (it was disabled until Agent Studio
316
- can reverse it) - create it right, or `update_navigation_route`, which replaces
317
- the whole route. Route descriptions are a retrieval surface: write what the
318
- page is *for*, in the user's words, and pair every parameterised route with the
319
- lookup action that produces its parameter — or ship neither.
320
-
321
- **Ask before publishing.** It is required only so the copilot can call the
322
- action, and it is a live product change when the agent has real users.
346
+ **The agent offers only what the browser has.** The SDK tells Foldspace the
347
+ key of every handler loaded on the page, and the agent offers only the
348
+ published actions among those keys - none, on a page that loads no handlers.
349
+ So a published action reaches someone only when its code is in what their
350
+ browser loads: the test window has it while attach runs; their users get it
351
+ once the code is in the bundle their site loads (**Done, and what you say at
352
+ the end**). A handler whose key does not match the action's key is never
353
+ offered.
354
+
355
+ A **new** action or task agent therefore changes nothing for their users until
356
+ its code reaches them. A navigation route needs no code: once both switches
357
+ below are on, it is live for them. An **edit to anything live** changes the
358
+ live agent at once - an action or task agent when you publish the edit
359
+ (above), a navigation route as soon as `update_navigation_route` replaces it.
360
+ **Navigation has two switches, and both must be on.** Each route has its own
361
+ `isActive`, set when you create it
362
+ (`create_navigation_route` with `is_active: true`, or `isActive: true` per route
363
+ in `bulk_create_navigation_routes`). The agent has one agent-level switch, **off
364
+ by default on a new subscription**: `publish_navigation` turns it on,
365
+ `unpublish_navigation` off, and neither touches the routes' own `isActive`.
366
+ `list_navigation_routes` shows both. To change one route's switch later,
367
+ `update_navigation_route` - it replaces the whole route, so read it first
368
+ (`get_navigation_route`) and send every field back. Route descriptions are a
369
+ retrieval surface: write what the page is *for*, in the user's words, and pair
370
+ every parameterised route with the lookup action that produces its parameter -
371
+ or ship neither.
372
+
373
+ **Ask before publishing.** It changes their Foldspace account, and an edit to
374
+ something live - or a new navigation route - reaches their users at once.
323
375
 
324
376
  ## Local loop
325
377
 
326
378
  ```bash
327
- npm run dev
379
+ npm run build
328
380
  npm run inject
329
381
  npx foldspace attach --daemon
330
382
  ```
331
383
 
332
384
  `inject` launches an isolated Chrome profile and records its debug port; it
333
385
  loads nothing. In a first session `attach` comes straight after sign-in (L0),
334
- and `foldspace observe` reads the same window while it runs. `attach` prepares the page and loads the local
335
- `dist/index.js` over CDP. Coding agents use `--daemon` so the tool returns after
336
- `[lifecycle] inspect_registration:…`. An empty local registry is valid; `npm run
337
- build` is still required so `dist/index.js` exists.
386
+ and `foldspace observe` reads the same window while it runs. `attach` prepares
387
+ the page and loads the local `dist/index.js` over CDP. Use `--daemon` so the
388
+ command returns after `[lifecycle] inspect_registration:…`. An empty local
389
+ registry is valid; `npm run build` is still required so `dist/index.js` exists.
338
390
 
339
391
  Attach mode is read off the page, never asked: default swap when the page
340
392
  already has the configured product and agent; `--bootstrap` when it has no
341
393
  Foldspace SDK; `--replace` when it embeds a different product or agent, or an
342
394
  SDK without the configured agent.
343
395
 
344
- `inspect_registration:registration_ok` is the signal. On `registration_mismatch`
345
- read `npx foldspace help attach --json` and map `missingActionNames`,
346
- `unexpectedActionNames`, `diagnosticError`. A running attach keeps the actions
347
- it loaded: after a rebuild, `attach --stop` and attach again to load the new
348
- ones. That, and checking published code on dev (**Done, and what you say at
349
- the end**), are the only reasons to stop it.
350
-
351
- Prove a named action through the visible agent: after the human talks to the
352
- copilot, read the daemon log for `[actions]` SDK callback and local
353
- execute/render lines. Those record names, statuses, durations and parameter keys
354
- only — never results or error bodies.
396
+ `inspect_registration:registration_ok` is the signal. On
397
+ `registration_mismatch`, the attach log says what differs - another agent or
398
+ product on the page, or action names that do not match - and the next step:
399
+ take it. A running attach keeps what it started with - the actions, the hosts
400
+ and the flags - so after a rebuild, `attach --stop` and attach again with the
401
+ same flags; after a change to `hosts`, `npm run inject` between the two
402
+ (**One product, several hosts**). Those, a mismatch's next step, and checking
403
+ published code on dev (**Done, and what you say at the end**) are the only
404
+ reasons to stop it.
405
+
406
+ Prove a named action through the visible agent. For your own chat turn,
407
+ `npx foldspace ask` reports whether `execute` and `render` ran; after a turn
408
+ of the human's, read the attach log for the `[actions]` SDK callback and local
409
+ execute/render lines. Those record names, statuses, durations and parameter
410
+ keys only - never results or error bodies.
355
411
 
356
412
  ## Done, and what you say at the end
357
413
 
@@ -420,17 +476,26 @@ Do not report success without all six:
420
476
  action the captured registry includes it.
421
477
  5. The action behaves against the real workflow: `[actions]
422
478
  local-handler:execute` in the attach log after `foldspace ask` (or the
423
- human) exercises the visible agent. A widget also needs `local-handler:render` from a real chat
424
- turn — not a hand-built `render()` call. `runAction` refuses render actions
425
- and driving `render()` yourself never runs `execute()`.
479
+ human) exercises the visible agent. A card also needs
480
+ `local-handler:render` from a real chat turn - not a hand-built `render()`
481
+ call. A view card leaves the chat free: in `ask`'s screenshot the panel's
482
+ message box does not read "Provide input, or cancel" beside a Cancel
483
+ button. If it does, `render` uses `callback` or `cancel` somewhere, or the
484
+ handler sets `awaitUserInput` - take them out. Then ask a follow-up
485
+ question about the card (`foldspace ask` again): the reply quotes its
486
+ numbers exactly as the card shows them. If it does not, make `execute`
487
+ return them as the card shows them (rule 2). An input card holds the chat
488
+ until the user answers or closes it, unless it calls `readyState`
489
+ (**Agentic UI components**).
426
490
  6. Browser evidence came from the live target, not hand-authored examples.
427
- Neighbour fixtures still pass when they exist.
428
491
 
429
492
  ## Rules that are not negotiable
430
493
 
431
494
  1. **Handlers return data only.** Never `directive`, `instructions`, or a
432
- paragraph telling the copilot what to say — those live in Agent Studio.
433
- Never return `ApiResult.detail` from `execute`; it is for the console.
495
+ paragraph telling the agent what to say - those live in Agent Studio.
496
+ Never return from `execute` a token, a cookie, any other secret or
497
+ `ApiResult.detail` (it is for the console): the agent reads what
498
+ `execute` returns for an action with no card or a view card (rule 2).
434
499
  Validate parameters and return sanitised errors.
435
500
  **What goes back to the agent states what happened - it never says what to
436
501
  do next.** A status is state: it succeeded, it failed and why (`reason`),
@@ -442,28 +507,36 @@ Do not report success without all six:
442
507
  `success: true`, the query, zero matches - *"No <records> match
443
508
  '<query>'."* - and it stops there.
444
509
  2. **`render` receives `execute`'s return value**, not the input params. A
445
- widget that lacks the ids the card needs passes in isolation and fails in the
446
- real chat. <https://docs.foldspace.ai/guides/in-chat-ui/>
510
+ card built from the params passes in isolation and fails in the real chat:
511
+ return from `execute` every id the card needs. For a view card the agent
512
+ gets that same value as soon as `render` returns and answers questions
513
+ about the card from it, so return the facts the card shows, as it shows
514
+ them, and those ids - never the raw API response. To change what the
515
+ agent gets, change `execute`, not the card (**Agentic UI components**).
516
+ <https://docs.foldspace.ai/guides/in-chat-ui/>
447
517
  3. **Say what you verified and what you assumed.** "The docs say X" and "I
448
518
  think X" are different claims — mark which. Search `docs.foldspace.ai` over
449
519
  MCP (`search_docs`, `fetch_page`) before asserting how the platform behaves;
450
520
  cite the page, or say plainly that the docs do not cover it and write down
451
521
  what you learned.
452
522
  4. **Test through the agent, then let them.** In a first session, prove an
453
- experience yourself before the customer sees it: once the action is
454
- published and registered, `npx foldspace ask "<the question>" --retry 2`
455
- types it into the agent's panel in the test window the way a person would,
456
- and reports the reply, the card's text, whether `execute` and `render` ran
457
- (from the attach log) and a screenshot. A reply with no action fired is
458
- publish lag or a wrong key - `--retry` covers the lag. Look at the
459
- screenshot. Only then the ready pill and the invitation to try it; never
460
- hand the first test to the customer. **Ask only what reads.** A question
461
- that makes the agent create, send, change or delete something is you
462
- writing in their product: ask it only in an environment the human has
463
- named as a test one, or let them type it. Those conversations appear in their
464
- Foldspace dashboard, and that is wanted: seeing their own conversations
465
- recorded is part of what they came to see. Never pass `--test-mode` in a
466
- first session.
523
+ experience yourself before the human sees it: once the action is published
524
+ and registered, `npx foldspace ask "<the question>" --retry 2` types it into
525
+ the agent's panel in the test window the way a person would, and reports the
526
+ reply, the card's text, whether `execute` and `render` ran (from the attach
527
+ log) and a screenshot. A reply with no action fired is publish lag or a wrong
528
+ key - `--retry` covers the lag. Look at the screenshot. Only then
529
+ `npx foldspace badge ready` and the invitation to try it; never hand the
530
+ first test to the human. **Ask only what reads.** A question that makes the
531
+ agent create, send, change or delete something is you writing in their
532
+ product: ask it only in an environment the human has named as a test one, or
533
+ let them type it. A card that writes only when the user approves it
534
+ (`recipes/create-a-record`) is the exception: the question draws the card,
535
+ `ask` presses nothing on it, and nothing is written - check what it shows,
536
+ and leave its buttons to the human. The conversations you start appear in
537
+ their Foldspace dashboard, and that is wanted: seeing their own
538
+ conversations recorded is part of what they came to see. Never pass
539
+ `--test-mode` in a first session.
467
540
  5. **Test mode is for an agent that already has real users.** Then `attach
468
541
  --test-mode` keeps your conversations out of their data. It is not a safety
469
542
  net: publishing still changes the live agent. Working on an agent in
@@ -472,9 +545,9 @@ Do not report success without all six:
472
545
  tool is the exception: it takes the same dev-then-production path as a new
473
546
  agent (**Done, and what you say at the end**).
474
547
  6. **The human is not necessarily a developer.** Never hand over a terminal
475
- line, a stack trace, or a choice between flags. Name experiences by outcome
476
- — *"show my plan and credits"*, not `get_account_status`. Their two jobs:
477
- sign in, and talk to the agent.
548
+ line, a stack trace, or a choice between flags. Name experiences by
549
+ outcome - *"show my plan and credits"*, not `get_account_status`. Their
550
+ jobs: sign in, talk to the agent, and say yes or no.
478
551
  7. **Be short.** No recap of what was established, no restating the plan before
479
552
  each step. Progress is one line: what you are doing, the level reached, and
480
553
  the question if there is one.
@@ -488,7 +561,7 @@ Do not report success without all six:
488
561
  and the recipes first: it may be supported in a way you have not found.
489
562
  Say "I did not find it", never "Foldspace does not support it".
490
563
  - **Build the narrowest workaround that gives them a working experience.**
491
- A prospect is never left stuck on a Foldspace limitation.
564
+ The human is never left stuck on a Foldspace limitation.
492
565
  - **Register it, in two places.** A comment in the code where it lives -
493
566
  what it works around, how you established that, and that it goes when the
494
567
  fix ships - and a row under **Workarounds** in `docs/app-profile.md`:
@@ -501,8 +574,10 @@ Do not report success without all six:
501
574
 
502
575
  Lint catches some of these in handler code; Agent Studio copy is on you.
503
576
 
504
- - **No emoji** in copilot replies, cards or handler strings. Put that in the
505
- agent's Behavior instructions, and in each new action's Studio `instructions`.
577
+ - **No emoji** in agent replies, cards or handler strings. Put that rule in
578
+ each new action's Studio `instructions` yourself. It reaches the agent's
579
+ other replies only if the human adds it to the Behavior instructions: offer
580
+ that in the second wave (item 5).
506
581
  - **Never send the user out of the host app.** No "open in <product>" button,
507
582
  no pasted URLs — navigate with a route, same tab.
508
583
 
@@ -527,26 +602,56 @@ wins**. <https://docs.foldspace.ai/customize/configuration/>
527
602
  mount code is the other place it can live, their engineers' call. Offer it in
528
603
  the second wave.
529
604
  - **An action's `execute` never calls `setConfiguration`**: a partial call from
530
- one once collapsed the agent's panel to nothing.
605
+ one can collapse the agent's panel to nothing.
531
606
 
532
607
  ## Agentic UI components: what bites
533
608
 
534
609
  An Agentic UI component is the card an action draws in the chat (`render`).
535
610
  Each of these cost a real build.
536
611
 
537
- - **A component that asks sets `awaitUserInput: true` - and then calling
538
- `callback` is mandatory, on every path.** The agent is paused until it is
539
- called: submitting, choosing "none of these" and closing the component all
540
- end in `callback` (or `cancel()` when they abort), or the conversation hangs.
541
- A component that only shows never sets the flag - it would hold the
542
- conversation until the user clicked an acknowledgement nobody needed.
543
- - **What a component sends back is the tool response: the data and the user's
544
- choice, as fields**, with the state - chosen, declined, failed - and nothing
545
- about what to do next (**Rules**, 1).
546
- - **A view-only component's data does not reach the model.** For an action
547
- that only shows something, the model is told just that *the information was
548
- displayed to the end-user* - nothing `execute` returned, nothing the component
549
- drew. So the component carries every fact, including the bad ones.
612
+ - **A card is a view card or an input card.** `render(data, host, header,
613
+ callback, cancel, readyState)` decides which: a `render` that uses
614
+ `callback`, `cancel` or `readyState` is an input card, and the agent waits
615
+ until the card reports through one of them; one that uses none of them is
616
+ a view card.
617
+ - **A view card shows something** - an account, a list, deals at risk -
618
+ with `render(data, host, header)`: no `callback`, no `cancel`. As soon as
619
+ `render` returns, the agent gets "Shown to the user. The user has not
620
+ acted on it." plus exactly what `execute` returned, and answers questions
621
+ about the card from that. So `execute` returns the facts the card shows,
622
+ as fields, and nothing else - or the failure the card draws with
623
+ `renderFailure`: `{ success: false, error, reason }`. To change what the
624
+ agent gets, change `execute`, not the card. Nothing disables a view card:
625
+ its tabs and links keep working.
626
+ - **An input card asks the user** - pick one, upload, create, approve. It
627
+ calls `callback` or `cancel` on every path: choosing, "none of these",
628
+ closing the card, failing. The agent waits until one of them runs; a path
629
+ that never calls back leaves it waiting until the user presses stop.
630
+ `callback(value)` reaches the agent as the user's answer and disables the
631
+ card; `callback(value, false)` keeps it usable. `cancel()` reaches the
632
+ agent as cancelled, with no data - when the agent needs to know how the
633
+ card was closed, call `callback` with that state instead, as
634
+ `pick-from-a-list` does (`cancelled: true`). ⚠️ The docs say the SDK also
635
+ draws a default "Cancel" control on a card; not yet checked in the live
636
+ product.
637
+ - **When the conversation should go on while the user decides,** an input
638
+ card calls `readyState(value)` (`render`'s 6th argument) once it is
639
+ drawn: the agent gets `value` as what is shown and carries on. ⚠️ The
640
+ user's answer then arrives as a follow-up turn - seen in the SDK's
641
+ headless mode (`mode: "HEADLESS"`, the customer's own chat UI), not yet
642
+ checked in the chat panel. No recipe uses it yet.
643
+ - **Never set `awaitUserInput`** on either kind: `render` decides whether
644
+ the agent waits. An input card does not need it. The chat panel ignores
645
+ it; in headless mode `awaitUserInput: true` on a view card leaves the
646
+ agent waiting for a callback that never comes, until the user presses
647
+ stop. Lint stops the build on `awaitUserInput: true` on a view card, and
648
+ warns on any other `awaitUserInput` and on a `render` that declares
649
+ `callback`, `cancel` or `readyState` but uses none of them (the SDK then
650
+ treats it as a view card).
651
+ - **What reaches the agent is the tool response: the data and the user's
652
+ choice, as fields**, with the state (chosen, declined, cancelled, failed)
653
+ and nothing about what to do next (**Rules**, 1). A view card needs no
654
+ state of its own: the SDK tells the agent it was shown.
550
655
  - **A failed or signed-out call is never drawn as zeros.** A signed-out response
551
656
  often looks like a valid empty one: a 200 with zeros, nulls or an empty list.
552
657
  Before you draw numbers, check that the response belongs to a signed-in user -
@@ -559,11 +664,15 @@ Each of these cost a real build.
559
664
  stays clean, and the action is recorded as succeeded. Only looking at it shows
560
665
  the problem. Draw the empty or the failed state (`renderEmpty`,
561
666
  `renderFailure`) - never nothing.
562
- - **Totals and money are never computed by the model.** Compute them in the
563
- action's code or in the component, from the API's own numbers, and show them
564
- in the component. The action's instructions tell the model not to restate or
565
- recompute them: a model that adds up line items will eventually add them up
566
- wrong.
667
+ - **Totals and money are never computed by the agent.** Compute them in
668
+ code, from the API's own numbers - in `execute` for a view card, since its
669
+ result is what the agent gets - show them on the card, and hand the agent
670
+ the same figures. In the action's Studio `instructions`, tell the agent to
671
+ answer follow-up questions from the data it receives, to quote money and
672
+ counts exactly as given, never to add them up itself (a model that adds up
673
+ line items will eventually add them up wrong), and, right after a view
674
+ card, to say one short sentence at most - never repeat what the card
675
+ shows.
567
676
  - **A card matches the panel, not the page.** The card sits in the agent's
568
677
  panel - white by default (the agent's theme: `backgroundColor #FFFFFF`,
569
678
  `primaryColor` for accents; a `darkTheme` when the panel is dark, and the
@@ -574,13 +683,8 @@ Each of these cost a real build.
574
683
  imports it, and the recipes' `brand.ts` files are placeholders for it. A
575
684
  dark app keeps its accent and font; its light-on-dark text would vanish on
576
685
  white, so that falls back to greys and the file says so. Record where the
577
- values came from in the profile's Brand section. Never copy colours by eye
578
- (seen 2026-09-22: a dark card in a white panel).
579
- - **Plain hex or rgba only.** The panel saves every card to the conversation
580
- history as an image drawn with html2canvas, which cannot read `oklch()`,
581
- `lab()` or `color-mix()`: one such value and the saved card is blank
582
- (`data:,` where the card was). Lint flags them. Do not read the app's CSS
583
- variables either - on modern apps they resolve to oklch.
686
+ values came from in the profile's Brand section. Never copy colours by eye:
687
+ that is how a dark card ends up in a white panel.
584
688
 
585
689
  ## Layout
586
690
 
@@ -602,20 +706,26 @@ Do not introduce another bundler or bundle format.
602
706
  ## Working examples to copy from
603
707
 
604
708
  Before writing a handler or a card, read
605
- `node_modules/@foldspace_npm/harness/recipes/INDEX.md` — the whole table, it is
606
- short — and open the recipe closest to the outcome. Each is a finished
607
- piece of a build, generalised from production builds with the customer taken
608
- out: the API helper, the handler or card, and a recorded response per state. Copy its `agent/` files across, then replace the
609
- placeholder path and fields with what you observed. They match the harness
610
- version you have installed, and every one is built and run on each release.
709
+ `node_modules/@foldspace_npm/harness/recipes/INDEX.md` - the whole table, it is
710
+ short - and open the recipe closest to the outcome. Each is a finished piece of
711
+ a build, generalised from production builds with the customer taken out: its
712
+ code and its fixtures (for most card recipes, a recorded response per state).
713
+ Copy its `agent/` files across, then replace the placeholder path and fields
714
+ with what you observed. They match the harness version you have installed, and
715
+ every one is built and run on each release.
611
716
 
612
717
  After the first experience → `who-is-the-user`. L2 → `find-by-name`, and
613
718
  `pick-from-a-list` when the user has to choose. The user's records on one
614
719
  card → `records-list`. The product lives on several hosts → `several-hosts`.
615
- Show one account on a card → `account-overview`. Upload a
720
+ The data arrives inside the HTML, with no JSON call → `server-rendered-forms`.
721
+ Show one account the user names → `lookup-then-view` (start here). The app
722
+ already hands over the id → `account-overview`. Upload a
616
723
  CSV of contacts → `upload-contacts`. Save a note on a meeting →
617
- `update-meeting-notes`. Prepare for a meeting → `prepare-for-a-meeting`. The records that need
618
- attention, with reasons → `opportunities-at-risk`.
724
+ `update-meeting-notes`. Create one record from what the user said, edited on a
725
+ card before it is written → `create-a-record` (all at once, or one question per
726
+ card then a review - its README says which). Prepare for a meeting →
727
+ `prepare-for-a-meeting`. The records that need attention, with reasons →
728
+ `opportunities-at-risk`.
619
729
  App does not use a bearer token → `swap-the-login-method`.
620
730
  Each says how many production builds carry the pattern and what they learned.
621
731
  If nothing fits — there is no L1 card recipe yet, and no navigation recipe —
@@ -632,11 +742,11 @@ not import it — fix the import when you implement.
632
742
  | `publicFetch` | Unauthenticated / marketing origin | Signed-in product APIs |
633
743
  | `getAuthToken` / `parseJwt` | `AUTH_SOURCE` is `localStorage` or `cookie` | Custom header schemes — override `apiFetch` in `utils.ts` |
634
744
  | `rankBy` | User typed a name; API search is exact or ignored | Domain ranking (invoices, coverage, industry classification codes) |
635
- | `renderLoading` / `Empty` / `Error` / `Fatal` | Chatterblock states | Branded cards — those stay in `agent/views/` |
745
+ | `renderLoading` / `Empty` / `Error` / `Fatal` | Plain states inside a card (loading, empty, error) | Branded cards - those stay in `agent/views/` |
636
746
  | `renderFailure` | Any `{ ok: false }` from `apiFetch` — it picks the state from `reason` | You need bespoke copy for one reason; then branch on `reason` yourself |
637
747
  | `httpFailure` / `networkFailure` | Your own `apiFetch` (custom header, cookies) — keeps the same `reason`s | The default `apiFetch` fits; then you never call these |
638
- | `getAgent` | Talking to Foldspace | You need a specific instance — then `agentIds()` |
639
- | `armAllInstances` | Attach/bootstrap setup | Inside `execute` |
748
+ | `getAgent` | Talking to Foldspace (the overlay instance) | You need a specific instance - then `window.foldspace.agent({ apiName, mode })`, with `apiName` the Agent Key and `mode` in capitals (`"EMBEDDED"`). `window.foldspace.agentIds()` lists the instances in lower case (`"embedded-<apiName>"`): ask only for one it lists - `agent()` creates an instance that does not exist |
749
+ | `armAllInstances` | Never in agent code - `attach` handles the page, and test mode is `attach --test-mode`, only for an agent that already has real users | Anywhere in `agent/`: it ships to production |
640
750
  | `redact` / `mapWithConcurrency` | Logging tokens; batching fetches | — |
641
751
 
642
752
  Every failed call carries a `reason` — `signed_out`, `forbidden`, `not_found`,
@@ -668,10 +778,13 @@ the package import is right.
668
778
  what you wanted it for, or take a narrower read-only route. Never retry it,
669
779
  and never try a different command hoping it gets through - not re-quoted, not
670
780
  in pieces, not through a helper script or a subagent: a denial covers the
671
- outcome, not the string. If a `foldspace` command itself was denied, name it,
672
- give the one-line allow rule (`Bash(npx foldspace:*)` in
673
- `.claude/settings.local.json`), and hand that step to the human - for a chat
674
- turn, that is them typing the question into the panel.
781
+ outcome, not the string. If the session's own checks - not the human -
782
+ denied a `foldspace` command other than `init`, `upgrade` or `deploy`, name
783
+ it and give the human the allow rule for that one command only, to add to
784
+ `.claude/settings.local.json` - for `observe`,
785
+ `Bash(npx foldspace observe:*)` - and meanwhile hand that step to them: for
786
+ a chat turn, that is them typing the question into the panel. `init`,
787
+ `upgrade` and `deploy` ask on purpose: never give an allow rule for them.
675
788
  - **Never create an account or a key** - not in Foldspace, not in the
676
789
  customer's product, not anywhere. If one is missing, say which and where it
677
790
  goes. **Never widen your own access**: if something is refused for a site or