@foldspace_npm/harness 0.1.14 → 0.1.16
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 +103 -19
- package/README.md +2 -2
- package/bin/attach.mjs +4 -1
- package/bin/inject.mjs +3 -0
- package/bin/observe.mjs +790 -0
- package/package.json +1 -1
- package/recipes/find-by-name/README.md +1 -1
- package/recipes/find-by-name/agent/actions/find_project_id.ts +7 -5
- package/src/cdp-client.mjs +233 -0
- package/src/cli-help.mjs +2 -1
- package/src/cli-registry.mjs +53 -3
- package/src/observe-core.mjs +627 -0
- package/src/session-events.mjs +42 -0
- package/templates/agent-starter/README.md +2 -2
package/CLAUDE.md
CHANGED
|
@@ -44,16 +44,23 @@ current CLI contract — risk, prerequisites, effects, next step.
|
|
|
44
44
|
| **L2 · look up entities** | a search from the user's own words, showing rows |
|
|
45
45
|
|
|
46
46
|
1. **Get the agent live first (L0) - with no actions.** Straight after
|
|
47
|
-
orienting: `npm run build`, `npm run inject`,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
orienting: `npm run build`, `npm run inject`, and one ask - *"A Chrome
|
|
48
|
+
window opened at <Product>'s login. Please log in there - I'll notice when
|
|
49
|
+
you're in."* Then `npx foldspace observe wait-login`: it returns the moment
|
|
50
|
+
they are in, so nobody has to type "I've logged in". Then
|
|
51
|
+
`npx foldspace attach --daemon`. An empty registry is valid. L0 is
|
|
52
|
+
`registration_ok` with the agent visible on their page: take
|
|
53
|
+
`npx foldspace observe screenshot`, send them the image, and tell them to
|
|
54
|
+
look at it and say hello to it. **From here on the agent stays on their
|
|
55
|
+
page** - you never detach to look at the app.
|
|
56
|
+
**Do not explore the app first**: no
|
|
51
57
|
reading its bundle, no watching its traffic, no browsing its screens.
|
|
52
58
|
Nothing about L0 needs any of it, and minutes of silent investigation
|
|
53
59
|
before the human has seen anything is the wrong first impression.
|
|
54
60
|
2. **Then offer experiences - from what you already have.** With the agent on
|
|
55
61
|
their screen, offer three or four read-only experiences as a choice,
|
|
56
|
-
recommended first. Sources: the
|
|
62
|
+
recommended first. Sources: the product's own menu (`npx foldspace observe
|
|
63
|
+
menu` takes a second and moves nothing), the conversation starters seeded at sign-up,
|
|
57
64
|
`discover_actions`, and `docs/app-profile.md`. `discover_actions` gives
|
|
58
65
|
candidates, not an inventory. Do not investigate the app to build this
|
|
59
66
|
list. Order them cheapest first: the user's own data with no parameter
|
|
@@ -92,15 +99,35 @@ and asking are not the only options — **observing is, and it comes first**:
|
|
|
92
99
|
|
|
93
100
|
1. **Already known** — `docs/app-profile.md`. A verified endpoint needs no
|
|
94
101
|
browsing.
|
|
95
|
-
2. **You
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
2. **You look - once they have chosen an experience - with `foldspace
|
|
103
|
+
observe`.** It reads the test window they signed in to, it is read-only by
|
|
104
|
+
construction, and it works **while the agent stays attached**: do not
|
|
105
|
+
`attach --stop` to look. Write no browser-driving script of your own, and do
|
|
106
|
+
not reach for a separate DevTools server - one launched on its own opens a
|
|
107
|
+
different, signed-out Chrome and can never see the window they logged in to.
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
npx foldspace observe menu # the product's own screens, by label and path
|
|
111
|
+
npx foldspace observe screen --click "<a label from menu>" --match "<three to five words for the data>"
|
|
112
|
+
npx foldspace observe read "<the GET path screen returned>" # GET only: status, field names and types, row count
|
|
113
|
+
npx foldspace observe auth # only if a read came back 401 or 403
|
|
114
|
+
npx foldspace observe styles # their font and colours, for the card
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`screen` reaches a screen the way a person does and returns at most eight
|
|
118
|
+
requests, ranked by your words - field names, value types and row counts,
|
|
119
|
+
**never values**. `read` confirms the one you picked. `auth` reports *where*
|
|
120
|
+
each header value comes from, never the value: do not go hunting through
|
|
121
|
+
storage, cookies or bundles yourself. One experience needs one screen; two
|
|
122
|
+
`screen` calls is normal, five means the experience is wrong for this
|
|
123
|
+
product - offer the next one.
|
|
124
|
+
|
|
125
|
+
**`observe` refuses what is not provably looking**, and a refusal is an
|
|
126
|
+
answer (**Safety**): it follows links, tabs and menu openers only - a plain
|
|
127
|
+
button can do anything, so use `--goto <path>` from `menu` instead; it will
|
|
128
|
+
not open a path that does something (`/logout`, `/…/delete`); and `read`
|
|
129
|
+
only goes to the app's own origin, a host configured for this project, or
|
|
130
|
+
an origin `screen` saw the page call.
|
|
104
131
|
3. **Ask the human to perform the workflow** — only for what you cannot reach:
|
|
105
132
|
an MFA step, a paywall, a screen that needs a record the account does not
|
|
106
133
|
have, or anything that would write.
|
|
@@ -198,9 +225,8 @@ npx foldspace attach --daemon
|
|
|
198
225
|
```
|
|
199
226
|
|
|
200
227
|
`inject` launches an isolated Chrome profile and records its debug port; it
|
|
201
|
-
loads nothing. In a first session `attach` comes straight after sign-in (L0)
|
|
202
|
-
|
|
203
|
-
same Chrome, then attach again. `attach` prepares the page and loads the local
|
|
228
|
+
loads nothing. In a first session `attach` comes straight after sign-in (L0),
|
|
229
|
+
and `foldspace observe` reads the same window while it runs. `attach` prepares the page and loads the local
|
|
204
230
|
`dist/index.js` over CDP. Coding agents use `--daemon` so the tool returns after
|
|
205
231
|
`[lifecycle] inspect_registration:…`. An empty local registry is valid; `npm run
|
|
206
232
|
build` is still required so `dist/index.js` exists.
|
|
@@ -212,8 +238,9 @@ SDK without the configured agent.
|
|
|
212
238
|
|
|
213
239
|
`inspect_registration:registration_ok` is the signal. On `registration_mismatch`
|
|
214
240
|
read `npx foldspace help attach --json` and map `missingActionNames`,
|
|
215
|
-
`unexpectedActionNames`, `diagnosticError`.
|
|
216
|
-
|
|
241
|
+
`unexpectedActionNames`, `diagnosticError`. A running attach keeps the actions
|
|
242
|
+
it loaded: after a rebuild, `attach --stop` and attach again to load the new
|
|
243
|
+
ones. That is the only reason to stop it.
|
|
217
244
|
|
|
218
245
|
Prove a named action through the visible agent: after the human talks to the
|
|
219
246
|
copilot, read the daemon log for `[actions]` SDK callback and local
|
|
@@ -244,6 +271,15 @@ Do not report success without all six:
|
|
|
244
271
|
paragraph telling the copilot what to say — those live in Agent Studio.
|
|
245
272
|
Never return `ApiResult.detail` from `execute`; it is for the console.
|
|
246
273
|
Validate parameters and return sanitised errors.
|
|
274
|
+
**What goes back to the agent states what happened - it never says what to
|
|
275
|
+
do next.** A status is state: it succeeded, it failed and why (`reason`),
|
|
276
|
+
or it found nothing. "Refine your query", "try again" and "ask the user for
|
|
277
|
+
the full name" are instructions, and an instruction inside a tool response
|
|
278
|
+
can make the model fire another tool call on its own. What the agent does
|
|
279
|
+
next is decided by the action's instructions in Agent Studio.
|
|
280
|
+
**A search that finds nothing is a valid result, not a failure**:
|
|
281
|
+
`success: true`, the query, zero matches - *"No <records> match
|
|
282
|
+
'<query>'."* - and it stops there.
|
|
247
283
|
2. **`render` receives `execute`'s return value**, not the input params. A
|
|
248
284
|
widget that lacks the ids the card needs passes in isolation and fails in the
|
|
249
285
|
real chat. <https://docs.foldspace.ai/guides/in-chat-ui/>
|
|
@@ -280,11 +316,59 @@ Lint catches some of these in handler code; Agent Studio copy is on you.
|
|
|
280
316
|
- **Never send the user out of the host app.** No "open in <product>" button,
|
|
281
317
|
no pasted URLs — navigate with a route, same tab.
|
|
282
318
|
|
|
319
|
+
## How the agent looks and opens is configuration
|
|
320
|
+
|
|
321
|
+
Three layers: built-in defaults, then Agent Studio, then code - **and code
|
|
322
|
+
wins**. <https://docs.foldspace.ai/customize/configuration/>
|
|
323
|
+
|
|
324
|
+
- **Agent-wide conversation starters you can set yourself**, through the
|
|
325
|
+
Foldspace tools, and they carry over to the customer's real site. Once the
|
|
326
|
+
first action works, offer one starter that asks its question, so they can
|
|
327
|
+
click instead of type. `set_conversation_starters` **replaces the whole set
|
|
328
|
+
and clears whatever you leave out**: call `list_conversation_starters` first
|
|
329
|
+
and send back the full list with your one addition. It changes their
|
|
330
|
+
Foldspace account, so it needs their yes.
|
|
331
|
+
- **Page-specific starters and the bottom bar are only possible from code
|
|
332
|
+
today** - `setConversationStarters` (`null` restores Agent Studio's), and
|
|
333
|
+
`setConfiguration({ bottomBarSettings })` then `openBottomBar()`. Send one
|
|
334
|
+
whole settings group per call, as the docs do.
|
|
335
|
+
- **That code belongs where the product mounts the agent - in the customer's
|
|
336
|
+
own source.** In this project you do not have their source: you see their
|
|
337
|
+
product only through the test window. So this is their engineers' change, not
|
|
338
|
+
yours. Shipping it from this project's start-up code instead is a fallback for
|
|
339
|
+
a customer who cannot make the change, decided with Foldspace - not something
|
|
340
|
+
to reach for, and never in a first session.
|
|
341
|
+
- **An action's `execute` never calls `setConfiguration`**: a partial call from
|
|
342
|
+
one once collapsed the agent's panel to nothing.
|
|
343
|
+
|
|
344
|
+
If they ask for page-specific starters or the bottom bar, say what it takes in
|
|
345
|
+
one sentence and note it in `docs/app-profile.md` as a next step.
|
|
346
|
+
|
|
283
347
|
## Agentic UI components: what bites
|
|
284
348
|
|
|
285
349
|
An Agentic UI component is the card an action draws in the chat (`render`).
|
|
286
350
|
Each of these cost a real build.
|
|
287
351
|
|
|
352
|
+
- **A component that asks sets `awaitUserInput: true` - and then calling
|
|
353
|
+
`callback` is mandatory, on every path.** The agent is paused until it is
|
|
354
|
+
called: submitting, choosing "none of these" and closing the component all
|
|
355
|
+
end in `callback` (or `cancel()` when they abort), or the conversation hangs.
|
|
356
|
+
A component that only shows never sets the flag - it would hold the
|
|
357
|
+
conversation until the user clicked an acknowledgement nobody needed.
|
|
358
|
+
- **What a component sends back is the tool response: the data and the user's
|
|
359
|
+
choice, as fields**, with the state - chosen, declined, failed - and nothing
|
|
360
|
+
about what to do next (**Rules**, 1).
|
|
361
|
+
- **A view-only component's data does not reach the model.** For an action
|
|
362
|
+
that only shows something, the model is told just that *the information was
|
|
363
|
+
displayed to the end-user* - nothing `execute` returned, nothing the component
|
|
364
|
+
drew. So the component carries every fact, including the bad ones.
|
|
365
|
+
- **A failed or signed-out call is never drawn as zeros.** A signed-out response
|
|
366
|
+
often looks like a valid empty one: a 200 with zeros, nulls or an empty list.
|
|
367
|
+
Before you draw numbers, check that the response belongs to a signed-in user -
|
|
368
|
+
an id or a name that has to be there. If it does not, that is `signed_out`:
|
|
369
|
+
return it as a failure (`success: false`, `reason`) and draw that state with
|
|
370
|
+
`renderFailure`. A balance of 0 shown to someone who is signed out is a wrong
|
|
371
|
+
answer reported as a success.
|
|
288
372
|
- **Every path through `render` draws something.** An action that returns
|
|
289
373
|
without drawing leaves the slot spinning forever: nothing throws, the console
|
|
290
374
|
stays clean, and the action is recorded as succeeded. Only looking at it shows
|
package/README.md
CHANGED
|
@@ -190,8 +190,8 @@ Use `foldspace help attach` for mode requirements, effects, and safety options.
|
|
|
190
190
|
Coding agents should run `foldspace attach --daemon` so the invoking tool
|
|
191
191
|
returns after `[lifecycle] inspect_registration:…`. Foreground attach remains
|
|
192
192
|
the default for humans watching the terminal. `attach --status` and
|
|
193
|
-
`attach --stop` inspect and tear down a daemon.
|
|
194
|
-
|
|
193
|
+
`attach --stop` inspect and tear down a daemon. Look at the customer's app with
|
|
194
|
+
`foldspace observe`: it reads the same window, read-only, while attach runs.
|
|
195
195
|
|
|
196
196
|
### Verify actions through the agent
|
|
197
197
|
|
package/bin/attach.mjs
CHANGED
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
shouldFulfillActionRequest,
|
|
44
44
|
} from "../src/attach-preflight.mjs";
|
|
45
45
|
import { serializeDiagnostic } from "../src/diagnostics.mjs";
|
|
46
|
+
import { recordEvent } from "../src/session-events.mjs";
|
|
46
47
|
import {
|
|
47
48
|
ERROR_CODES,
|
|
48
49
|
createLifecycleResult,
|
|
@@ -699,6 +700,8 @@ async function prepare(ws, sessionId, url) {
|
|
|
699
700
|
|
|
700
701
|
{
|
|
701
702
|
const verification = await verifyPreparedPage(ws, sessionId);
|
|
703
|
+
// For scoring a session: the moment the customer could first see their agent.
|
|
704
|
+
if (verification.ok) recordEvent(root, "agent_visible", { mode: attachMode });
|
|
702
705
|
logLifecycle(
|
|
703
706
|
createLifecycleResult({
|
|
704
707
|
operation: "inspect_registration",
|
|
@@ -952,7 +955,7 @@ console.log(`Test: ${noTestMode ? "OFF — conversations WILL appear in the das
|
|
|
952
955
|
console.log(`Hosts: ${hostPatterns.join(", ")}`);
|
|
953
956
|
console.log(`Serving: ${path.relative(root, bundlePath)}`);
|
|
954
957
|
console.log(
|
|
955
|
-
`CDP:
|
|
958
|
+
`CDP: attached. \`foldspace observe\` reads this same window while attach runs; stop attach only to load rebuilt actions.\n`,
|
|
956
959
|
);
|
|
957
960
|
|
|
958
961
|
function printSessionSummary() {
|
package/bin/inject.mjs
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* npm run inject -- --url https://app.other.com --product ABC123 --agent other-agent
|
|
12
12
|
* npm run inject -- --print # resolve target only, don't launch
|
|
13
13
|
*/
|
|
14
|
+
import { recordEvent } from "../src/session-events.mjs";
|
|
14
15
|
import fs from "fs";
|
|
15
16
|
import path from "path";
|
|
16
17
|
import os from "os";
|
|
@@ -279,6 +280,8 @@ fs.writeFileSync(
|
|
|
279
280
|
),
|
|
280
281
|
);
|
|
281
282
|
|
|
283
|
+
recordEvent(root, "chrome_opened", { url: startUrl });
|
|
284
|
+
|
|
282
285
|
// --- theme -------------------------------------------------------------
|
|
283
286
|
//
|
|
284
287
|
// Chrome 151 ignores --load-extension, so load the optional cosmetic theme
|