@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.
- package/CLAUDE.md +287 -178
- package/README.md +1 -1
- package/package.json +2 -1
- package/recipes/INDEX.md +15 -0
- package/recipes/README.md +46 -0
- package/recipes/find-by-name/README.md +47 -0
- package/recipes/find-by-name/agent/actions/find_project_id.ts +101 -0
- package/recipes/find-by-name/agent/api/projects.ts +36 -0
- package/recipes/find-by-name/agent/projects.ts +40 -0
- package/recipes/find-by-name/fixtures/projects.all.json +29 -0
- package/recipes/find-by-name/fixtures/projects.empty-account.json +4 -0
- package/recipes/find-by-name/fixtures/projects.none.json +4 -0
- package/recipes/find-by-name/recipe.json +10 -0
- package/recipes/pick-from-a-list/README.md +44 -0
- package/recipes/pick-from-a-list/agent/actions/choose_project.ts +161 -0
- package/recipes/pick-from-a-list/agent/api/projects.ts +36 -0
- package/recipes/pick-from-a-list/agent/projects.ts +40 -0
- package/recipes/pick-from-a-list/agent/views/brand.ts +14 -0
- package/recipes/pick-from-a-list/agent/views/picker.ts +119 -0
- package/recipes/pick-from-a-list/fixtures/projects.all.json +29 -0
- package/recipes/pick-from-a-list/fixtures/projects.empty-account.json +4 -0
- package/recipes/pick-from-a-list/recipe.json +10 -0
- package/recipes/swap-the-login-method/README.md +40 -0
- package/recipes/swap-the-login-method/agent/utils.ts +73 -0
- package/recipes/swap-the-login-method/fixtures/anything.ok.json +8 -0
- package/recipes/swap-the-login-method/recipe.json +12 -0
- package/recipes/swap-the-login-method/variants/utils.cookies.ts +64 -0
- package/recipes/who-is-the-user/README.md +56 -0
- package/recipes/who-is-the-user/agent/identify.ts +89 -0
- package/recipes/who-is-the-user/fixtures/profile.ok.json +6 -0
- package/recipes/who-is-the-user/recipe.json +9 -0
- package/src/runtime/config.ts +1 -1
- package/src/runtime/http.ts +104 -53
- package/src/runtime/index.ts +4 -1
- package/src/runtime/match.ts +1 -1
- package/src/runtime/render.ts +42 -1
- package/templates/agent-starter/agent/actions/_example.ts +9 -2
- package/templates/agent-starter/agent/utils.ts +2 -0
package/CLAUDE.md
CHANGED
|
@@ -1,108 +1,184 @@
|
|
|
1
1
|
# Foldspace Agent
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
`foldspace upgrade --
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
`
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
123
|
-
agent
|
|
124
|
-
`--replace` when it embeds a different product or agent, or
|
|
125
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
|
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
|
|
141
|
-
|
|
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 <product>"
|
|
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.
|
|
162
|
-
|
|
163
|
-
2. `foldspace lint` reports no errors (`foldspace build` runs
|
|
164
|
-
3. The
|
|
165
|
-
4. The browser reports `inspect_registration:registration_ok
|
|
166
|
-
action
|
|
167
|
-
5. The action behaves
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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` —
|
|
182
|
-
|
|
183
|
-
- `
|
|
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`.
|
|
192
|
-
|
|
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,
|
|
201
|
-
| `renderLoading` / `Empty` / `Error` / `Fatal` | Chatterblock
|
|
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`
|
|
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.
|
|
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.
|
|
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
|
],
|
package/recipes/INDEX.md
ADDED
|
@@ -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.
|