@foldspace_npm/harness 0.1.14 → 0.1.15

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 CHANGED
@@ -244,6 +244,15 @@ Do not report success without all six:
244
244
  paragraph telling the copilot what to say — those live in Agent Studio.
245
245
  Never return `ApiResult.detail` from `execute`; it is for the console.
246
246
  Validate parameters and return sanitised errors.
247
+ **What goes back to the agent states what happened - it never says what to
248
+ do next.** A status is state: it succeeded, it failed and why (`reason`),
249
+ or it found nothing. "Refine your query", "try again" and "ask the user for
250
+ the full name" are instructions, and an instruction inside a tool response
251
+ can make the model fire another tool call on its own. What the agent does
252
+ next is decided by the action's instructions in Agent Studio.
253
+ **A search that finds nothing is a valid result, not a failure**:
254
+ `success: true`, the query, zero matches - *"No <records> match
255
+ '<query>'."* - and it stops there.
247
256
  2. **`render` receives `execute`'s return value**, not the input params. A
248
257
  widget that lacks the ids the card needs passes in isolation and fails in the
249
258
  real chat. <https://docs.foldspace.ai/guides/in-chat-ui/>
@@ -280,11 +289,59 @@ Lint catches some of these in handler code; Agent Studio copy is on you.
280
289
  - **Never send the user out of the host app.** No "open in <product>" button,
281
290
  no pasted URLs — navigate with a route, same tab.
282
291
 
292
+ ## How the agent looks and opens is configuration
293
+
294
+ Three layers: built-in defaults, then Agent Studio, then code - **and code
295
+ wins**. <https://docs.foldspace.ai/customize/configuration/>
296
+
297
+ - **Agent-wide conversation starters you can set yourself**, through the
298
+ Foldspace tools, and they carry over to the customer's real site. Once the
299
+ first action works, offer one starter that asks its question, so they can
300
+ click instead of type. `set_conversation_starters` **replaces the whole set
301
+ and clears whatever you leave out**: call `list_conversation_starters` first
302
+ and send back the full list with your one addition. It changes their
303
+ Foldspace account, so it needs their yes.
304
+ - **Page-specific starters and the bottom bar are only possible from code
305
+ today** - `setConversationStarters` (`null` restores Agent Studio's), and
306
+ `setConfiguration({ bottomBarSettings })` then `openBottomBar()`. Send one
307
+ whole settings group per call, as the docs do.
308
+ - **That code belongs where the product mounts the agent - in the customer's
309
+ own source.** In this project you do not have their source: you see their
310
+ product only through the test window. So this is their engineers' change, not
311
+ yours. Shipping it from this project's start-up code instead is a fallback for
312
+ a customer who cannot make the change, decided with Foldspace - not something
313
+ to reach for, and never in a first session.
314
+ - **An action's `execute` never calls `setConfiguration`**: a partial call from
315
+ one once collapsed the agent's panel to nothing.
316
+
317
+ If they ask for page-specific starters or the bottom bar, say what it takes in
318
+ one sentence and note it in `docs/app-profile.md` as a next step.
319
+
283
320
  ## Agentic UI components: what bites
284
321
 
285
322
  An Agentic UI component is the card an action draws in the chat (`render`).
286
323
  Each of these cost a real build.
287
324
 
325
+ - **A component that asks sets `awaitUserInput: true` - and then calling
326
+ `callback` is mandatory, on every path.** The agent is paused until it is
327
+ called: submitting, choosing "none of these" and closing the component all
328
+ end in `callback` (or `cancel()` when they abort), or the conversation hangs.
329
+ A component that only shows never sets the flag - it would hold the
330
+ conversation until the user clicked an acknowledgement nobody needed.
331
+ - **What a component sends back is the tool response: the data and the user's
332
+ choice, as fields**, with the state - chosen, declined, failed - and nothing
333
+ about what to do next (**Rules**, 1).
334
+ - **A view-only component's data does not reach the model.** For an action
335
+ that only shows something, the model is told just that *the information was
336
+ displayed to the end-user* - nothing `execute` returned, nothing the component
337
+ drew. So the component carries every fact, including the bad ones.
338
+ - **A failed or signed-out call is never drawn as zeros.** A signed-out response
339
+ often looks like a valid empty one: a 200 with zeros, nulls or an empty list.
340
+ Before you draw numbers, check that the response belongs to a signed-in user -
341
+ an id or a name that has to be there. If it does not, that is `signed_out`:
342
+ return it as a failure (`success: false`, `reason`) and draw that state with
343
+ `renderFailure`. A balance of 0 shown to someone who is signed out is a wrong
344
+ answer reported as a success.
288
345
  - **Every path through `render` draws something.** An action that returns
289
346
  without drawing leaves the slot spinning forever: nothing throws, the console
290
347
  stays clean, and the action is recorded as succeeded. Only looking at it shows
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foldspace_npm/harness",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "Build and verify portable Foldspace action artifacts against a live app.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -18,7 +18,7 @@ parameter `name` — *the name, or part of the name, the user said*.
18
18
  | no name ("how many projects do I have?") | the account's **count**, plus a small sample — never the whole account |
19
19
  | one clear match | that project, its match strength, and up to three alternatives |
20
20
  | several equally good matches | **all of them**, marked `ambiguous` — never a silent pick |
21
- | nothing matches | `success: false`, plus near-misses for a typo |
21
+ | nothing matches | `success: true` with zero `matches` - an empty search is a valid result, not a failure - plus near-misses for a typo, as data. Never "refine your query" |
22
22
  | the account is empty | a count of zero — not an error |
23
23
  | signed out, no access, throttled, unreachable | `success: false` with the `reason` |
24
24
 
@@ -48,15 +48,17 @@ export const find_project_id = {
48
48
 
49
49
  if (summaries.length === 0) {
50
50
  // Server search is a substring of the name, so a typo finds nothing.
51
- // Near-misses give the agent something true to work with but they are
51
+ // An empty search is a VALID result, not a failure: say that nothing
52
+ // matched and stop. Never "refine your query" or "try again" - an
53
+ // instruction in a tool response can make the model call a tool again on
54
+ // its own. Near-misses are facts, so they go back as data - but they are
52
55
  // near-misses, not a resolution.
53
56
  const wide = await listProjects({ pageSize: SHORTLIST });
54
57
  const near = wide.ok ? nearMatches(wide.data.projects.map(toSummary), query, 5) : [];
55
58
  return {
56
- success: false,
57
- error: `No project matching "${query}".`,
58
- reason: "not_found" as const,
59
- data: { query, total: res.data.total, nearMatches: near },
59
+ success: true,
60
+ message: `No projects match "${query}".`,
61
+ data: { query, total: res.data.total, matches: [], nearMatches: near },
60
62
  };
61
63
  }
62
64