@foldspace_npm/harness 0.1.10 → 0.1.12

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 (45) hide show
  1. package/CLAUDE.md +328 -0
  2. package/README.md +24 -5
  3. package/bin/cli.mjs +29 -1
  4. package/package.json +4 -2
  5. package/recipes/INDEX.md +15 -0
  6. package/recipes/README.md +46 -0
  7. package/recipes/find-by-name/README.md +47 -0
  8. package/recipes/find-by-name/agent/actions/find_project_id.ts +101 -0
  9. package/recipes/find-by-name/agent/api/projects.ts +36 -0
  10. package/recipes/find-by-name/agent/projects.ts +40 -0
  11. package/recipes/find-by-name/fixtures/projects.all.json +29 -0
  12. package/recipes/find-by-name/fixtures/projects.empty-account.json +4 -0
  13. package/recipes/find-by-name/fixtures/projects.none.json +4 -0
  14. package/recipes/find-by-name/recipe.json +10 -0
  15. package/recipes/pick-from-a-list/README.md +44 -0
  16. package/recipes/pick-from-a-list/agent/actions/choose_project.ts +161 -0
  17. package/recipes/pick-from-a-list/agent/api/projects.ts +36 -0
  18. package/recipes/pick-from-a-list/agent/projects.ts +40 -0
  19. package/recipes/pick-from-a-list/agent/views/brand.ts +14 -0
  20. package/recipes/pick-from-a-list/agent/views/picker.ts +119 -0
  21. package/recipes/pick-from-a-list/fixtures/projects.all.json +29 -0
  22. package/recipes/pick-from-a-list/fixtures/projects.empty-account.json +4 -0
  23. package/recipes/pick-from-a-list/recipe.json +10 -0
  24. package/recipes/swap-the-login-method/README.md +40 -0
  25. package/recipes/swap-the-login-method/agent/utils.ts +73 -0
  26. package/recipes/swap-the-login-method/fixtures/anything.ok.json +8 -0
  27. package/recipes/swap-the-login-method/recipe.json +12 -0
  28. package/recipes/swap-the-login-method/variants/utils.cookies.ts +64 -0
  29. package/recipes/who-is-the-user/README.md +56 -0
  30. package/recipes/who-is-the-user/agent/identify.ts +89 -0
  31. package/recipes/who-is-the-user/fixtures/profile.ok.json +6 -0
  32. package/recipes/who-is-the-user/recipe.json +9 -0
  33. package/src/cli-help.mjs +1 -0
  34. package/src/cli-registry.mjs +48 -3
  35. package/src/init.mjs +16 -8
  36. package/src/runtime/config.ts +1 -1
  37. package/src/runtime/http.ts +104 -53
  38. package/src/runtime/index.ts +4 -1
  39. package/src/runtime/match.ts +1 -1
  40. package/src/runtime/render.ts +42 -1
  41. package/src/upgrade.mjs +469 -0
  42. package/templates/agent-starter/CLAUDE.md +11 -220
  43. package/templates/agent-starter/README.md +1 -1
  44. package/templates/agent-starter/agent/actions/_example.ts +9 -2
  45. package/templates/agent-starter/agent/utils.ts +2 -0
@@ -1,225 +1,16 @@
1
- # Foldspace browser actions
1
+ # Foldspace actions for {{APP_DOMAIN}}
2
2
 
3
- ## Scope
3
+ @node_modules/@foldspace_npm/harness/CLAUDE.md
4
4
 
5
- This project contains Foldspace action handlers. The customer website, SDK
6
- snippet, `identify()` call, and layout are out of scope unless the user asks for
7
- changes there.
5
+ If that import did not resolve, read `node_modules/@foldspace_npm/harness/CLAUDE.md`
6
+ before building. What follows is only what is true of **this** product.
8
7
 
9
- ## Before building
8
+ ## This product
10
9
 
11
- 1. Run `npx foldspace help --json` before an unfamiliar harness operation.
12
- Treat its risk, prerequisites, effects, and next-step fields as the current
13
- CLI contract.
14
- 2. Connect Product MCP and verify that `list_agents` works.
15
- 3. Inspect the selected agent with `get_agent_settings`, `list_actions`, and
16
- `list_task_agents`.
17
- 4. Use `discover_actions` to identify candidate experiences, then let the user
18
- choose what to build. Treat those ideas as candidates, not an inventory.
10
+ | | |
11
+ |---|---|
12
+ | Agent | `{{AGENT_API_NAME}}` |
13
+ | Product | `{{PRODUCT_ID}}` |
14
+ | App | `{{APP_DOMAIN}}` |
19
15
 
20
- ## Build workflow
21
-
22
- An experience can require several reusable actions. Separate lookup actions
23
- from actions that read or mutate a selected resource.
24
-
25
- 1. Agree on the candidate experience and how its actions would compose. This is
26
- not a publish and not a commitment.
27
- 2. Run `npm run inject`. Ask the user to sign in and perform the real target
28
- workflow in that Chrome window.
29
- 3. Capture at least one real HTTP 200 for the data the action needs. Use
30
- chrome-devtools MCP against the inject Chrome, or a page-context `fetch`,
31
- **before** `attach` owns the debug port. If there is no 200,
32
- pivot; do not create Foldspace resources. Write what you establish into
33
- `docs/app-profile.md` with **how you know it**. Do not promote an assumption
34
- by quoting that file.
35
- 4. Create action metadata as a draft. `generate_action_handler` works from the
36
- draft schema; do not publish yet.
37
- 5. Implement the handler using the observed request and response shapes.
38
- Import helpers from `agent/utils.ts` — inspect that file before writing
39
- another `fetch` or rank helper.
40
- 6. Register the handler in `agent/actions/index.ts` and build (`foldspace build` lints first).
41
- 7. Run `npx foldspace attach --daemon` (add `--bootstrap` or `--replace` when
42
- the page requires it). An empty local registry is valid if you only want to
43
- see how the agent works.
44
- 8. Ask before publishing. Publishing is required only so the copilot can call
45
- the action, and it is a live product change when the agent has real users.
46
- 9. Complete the verification gates below.
47
-
48
- ## API rules
49
-
50
- Actions execute in the user's signed-in browser session.
51
-
52
- - Import HTTP, ranking, and widget helpers from `../utils`, not a new
53
- `agent/api.ts` copy of `fetch`.
54
- - Do not guess `API_BASE` or `AUTH_SOURCE`. Capture at least one real 200
55
- (and the auth header the page actually sends) before filling them in.
56
- `credentials: "include"` is correct only when you have observed the app
57
- using cookies that way. Many apps use `Authorization: Bearer` from
58
- `localStorage` instead; some use a custom header.
59
- - Never guess endpoints or schemas. Capture at least one real 200 before
60
- implementing a parser. Do not implement a path that was not observed.
61
- - Verify that the user is signed in before observing a workflow.
62
- - Do not substitute a public developer API when the browser session is missing;
63
- ask the user to sign in.
64
- - Validate parameters and return sanitized errors. Return **data only** — never
65
- `directive`, `instructions`, or a paragraph telling the copilot what to say.
66
- Action and agent instructions live in Agent Studio / MCP. Never return
67
- `ApiResult.detail` from `execute` — it is for the console.
68
- - Actions that return data the user will inspect should include a `render`
69
- function for in-chat UI (a chatterblock). If it makes more sense to output the data
70
- in a UI component instead of text then consider using render to show a component.
71
- - `render` receives **`execute`'s return value**, not the action's input params.
72
- Returning anything that lacks the ids the card needs is why widgets pass in
73
- isolation and fail in the real chat.
74
- - `runAction` refuses render actions (`cannot be executed silently`). Driving
75
- `render()` yourself never runs `execute()`, so it does not test that contract.
76
- - Check https://docs.foldspace.ai/guides/in-chat-ui/ for more information
77
-
78
- ## Task agents
79
-
80
- Use a Task Agent when the handler needs a one-time LLM subtask that
81
- deterministic code cannot do well: extraction, summarization,
82
- classification, normalization, enrichment, or generation.
83
-
84
- Do not use a Task Agent for API calls, CRUD, routing, or parsing a
85
- known response shape. Those stay in `execute`.
86
-
87
- Task agents are created in Agent Studio, not in this repo. Ask before
88
- creating or publishing one. Call a published task agent from the
89
- handler with `runTask({ taskKey, data })` (if not published the runTask won't work).
90
- `data` carries extracted facts only — not `prompt` / `instructions` strings.
91
- Prefer JSON output when the handler must consume the result.
92
-
93
- See https://docs.foldspace.ai/user-guides/task-agents/ and
94
- https://docs.foldspace.ai/reference/task-agent-api/
95
-
96
- ## Local harness loop
97
-
98
- ```bash
99
- npm run dev
100
- npm run inject
101
- npx foldspace attach --daemon
102
- ```
103
-
104
- `inject` launches an isolated Chrome profile and records its debug port.
105
- It does not generate or load an application extension. Observe the customer's
106
- workflow after inject and **before** attach, while chrome-devtools MCP can use
107
- the same Chrome. `attach` prepares the page and loads the local `dist/index.js`
108
- bundle through CDP. Coding agents must use `--daemon` so the invoking tool
109
- returns after `[lifecycle] inspect_registration:…`; foreground `npm run attach`
110
- is for humans watching the terminal. An empty local registry is valid. `npm run
111
- build` is still required so `dist/index.js` exists.
112
-
113
- Use the default swap only when the page already has the configured product and
114
- agent. Use `--bootstrap` only when the page has no Foldspace SDK, and
115
- `--replace` when it embeds a different product or agent, or when an SDK is
116
- present without the configured agent.
117
-
118
- The attach log must report `inspect_registration:registration_ok` before
119
- treating the page as registered. Zero captured actions is success when the
120
- local registry is empty. On `registration_mismatch`, read
121
- `npx foldspace help attach --json` diagnostics and map those names onto the
122
- lifecycle details (`missingActionNames`, `unexpectedActionNames`,
123
- `diagnosticError`).
124
-
125
- While attach is running it owns the debug port. Detach (`attach --stop`, or
126
- Ctrl-C in the foreground) before using chrome-devtools MCP against the same
127
- Chrome.
128
-
129
- Prove a named action through the visible agent. After the user talks to the
130
- copilot, read the daemon log for `[actions]` SDK callback and local
131
- execute/render lines; do not invoke the handler directly. Those lines record
132
- names, statuses, durations, and parameter keys only — not results or error
133
- bodies.
134
-
135
- ## Product defaults
136
-
137
- These are not Joist-specific. Lint can catch some of them in handler code;
138
- Agent Studio copy is on you.
139
-
140
- - **No emoji** in copilot replies, cards, or handler strings. Put that in the
141
- agent's Behavior instructions (MCP cannot write that field, so each new
142
- action's Studio `instructions` must carry the line too).
143
- - **Never send the user out of the host app.** No "open in <product>"
144
- button and no pasted URLs — navigate with a navigation route, same tab.
145
- - **Check row counts before choosing the experience.** On an empty account the
146
- useful first action is one that creates data.
147
-
148
- ## Verification gates
149
-
150
- Do not report success without all six:
151
-
152
- 1. TypeScript compiles with `npm run typecheck` (`tsc --noEmit -p tsconfig.json`).
153
- Do not run `npx tsc` — that can install the wrong package.
154
- 2. `foldspace lint` reports no errors (`foldspace build` runs this first).
155
- 3. The expected handler appears in `dist/index.js`.
156
- 4. The browser reports `inspect_registration:registration_ok`. For a named
157
- action, the captured registry includes that handler.
158
- 5. The action behaves correctly against the real target workflow. Confirm
159
- `[actions] local-handler:execute` in the attach log after the user exercises
160
- the visible agent. For a widget, that log must include `local-handler:render`
161
- from a real chat turn — not a hand-built `render()` call.
162
- 6. Browser evidence came from the live target, not from hand-authored examples.
163
- Neighbour fixtures still pass when they exist.
164
-
165
- ## Layout
166
-
167
- - `agent/actions/` — one handler per action (`execute`, optional `render`),
168
- registered in `index.ts`
169
- - `agent/api/` — one HTTP helper per endpoint
170
- - `agent/constants.ts` — agent, product, domain, plus empty `API_BASE` /
171
- `AUTH_SOURCE` and `LOAD_MODE`
172
- - `agent/utils.ts` — configure the harness runtime and re-export it. **This is
173
- the only import surface for actions.**
174
- - `foldspace.dev.json` — local harness target configuration
175
- - `docs/app-profile.md` — what is known about this app, and how it was established
176
-
177
- Do not introduce another bundler or bundle format.
178
-
179
- ## What you already have
180
-
181
- Import from `../utils`. Inspect that file before implementing another
182
- general-purpose helper. MCP `generate_action_handler` may still emit a
183
- skeleton that does not import it — fix the import when you implement.
184
-
185
- | Helper | Use when | Do not use when |
186
- |---|---|---|
187
- | `apiFetch` / `apiFetchBinary` | The app's own JSON/file API, after a real 200 | Public marketing hosts (`publicFetch`); custom auth headers |
188
- | `publicFetch` | Unauthenticated / marketing origin | Signed-in product APIs |
189
- | `getAuthToken` / `parseJwt` | `AUTH_SOURCE` is `localStorage` or `cookie` | Custom header schemes — override `apiFetch` in `utils.ts` |
190
- | `rankBy` | User typed a name; API search is exact or ignored | Domain ranking (invoices, coverage, MasterFormat) |
191
- | `renderLoading` / `Empty` / `Error` / `Fatal` | Chatterblock empty/error paths | Branded cards — those stay in `agent/views/` |
192
- | `getAgent` | Talking to Foldspace | You need a specific instance — then `agentIds()` |
193
- | `armAllInstances` | Attach/bootstrap setup | Inside `execute` (can ship into `dist`; attach already arms test mode) |
194
- | `redact` / `mapWithConcurrency` | Logging tokens; batching fetches | — |
195
-
196
- If the observed auth is not Bearer + `localStorage`/`cookie`, stop re-exporting
197
- that one function and keep the rest:
198
-
199
- ```ts
200
- export { getAgent, rankBy, renderEmpty } from "@foldspace_npm/harness/runtime";
201
- export { apiFetch } from "./api";
202
- ```
203
-
204
- ## Agent learnings
205
-
206
- `docs/app-profile.md` is the durable record of this app. Fill it as you probe,
207
- not afterwards. Other notes in `docs/` are fine for session-specific lessons.
208
-
209
- - Before similar work, read `docs/app-profile.md` and any other notes in `docs/`.
210
- - After non-obvious discoveries, add them to the profile (or a short extra
211
- note) covering verified gotchas, failed approaches, and how they were
212
- established.
213
- - Keep notes concise and evidence-based.
214
- - Do not store secrets, cookies, HAR files, browser storage, or other transient
215
- session data in `docs/`.
216
-
217
- ## Safety
218
-
219
- - Do not commit secrets, cookies, HAR files, browser storage, or
220
- `.foldspace-dev/`.
221
- - Local handler changes are not cloud publication.
222
- - Creating or publishing Foldspace resources requires explicit approval.
223
- - Action keys and parameter names must match the published schema.
224
- - Search Foldspace documentation before asserting unfamiliar platform
225
- behaviour.
16
+ Observed API, auth, and account facts for this app live in `docs/app-profile.md`.
@@ -28,7 +28,7 @@ agent/
28
28
  utils.ts Foldspace agent lookup
29
29
  docs/ optional notes for coding-agent learnings (create when needed)
30
30
  foldspace.dev.json
31
- CLAUDE.md
31
+ CLAUDE.md imports platform instructions from the installed harness
32
32
  ```
33
33
 
34
34
  ## Agent learnings
@@ -1,6 +1,9 @@
1
1
  // Copy this file to <action_key>.ts and register it in index.ts.
2
2
  // Do not register _example — it is not a real Agent Studio action.
3
3
  //
4
+ // This is an empty skeleton. For a finished experience — handler, card with
5
+ // every state, fixtures — read node_modules/@foldspace_npm/harness/recipes/INDEX.md.
6
+ //
4
7
  // Import helpers from ../utils, not from @foldspace_npm/harness/runtime.
5
8
  // The path below is a placeholder until you capture a real HTTP 200.
6
9
 
@@ -14,7 +17,9 @@ export const example_action = {
14
17
 
15
18
  const result = await apiFetch<{ items?: Item[] }>("/__observe_me");
16
19
  if (!result.ok) {
17
- return { ok: false, error: result.error };
20
+ // Pass `reason` through so `render` can call renderFailure — an expired
21
+ // session, a 404 and a throttled call are three different widget states.
22
+ return { ok: false, error: result.error, reason: result.reason };
18
23
  }
19
24
 
20
25
  const items = result.data.items ?? [];
@@ -23,6 +28,8 @@ export const example_action = {
23
28
  idOf: (item) => item.id,
24
29
  });
25
30
 
26
- return { ok: true, matches };
31
+ // Zero matches is an answer, not an error. Return the query so the empty
32
+ // state can name what was searched for.
33
+ return { ok: true, query, matches };
27
34
  },
28
35
  };
@@ -27,6 +27,8 @@
27
27
  *
28
28
  * Widget chrome (unstyled; replace in `agent/views/` when brand matters):
29
29
  * - `renderLoading` / `renderEmpty` / `renderError` / `renderFatal`
30
+ * - `renderFailure` — picks one of those from `ApiFailure.reason`, so an expired
31
+ * session, a 404 and a throttled call each draw the right state.
30
32
  *
31
33
  * Config (already called above; you rarely need these in an action):
32
34
  * - `configure` / `getConfig`