@foldspace_npm/harness 0.1.8 → 0.1.10

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.
@@ -35,6 +35,8 @@ from actions that read or mutate a selected resource.
35
35
  4. Create action metadata as a draft. `generate_action_handler` works from the
36
36
  draft schema; do not publish yet.
37
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.
38
40
  6. Register the handler in `agent/actions/index.ts` and build (`foldspace build` lints first).
39
41
  7. Run `npx foldspace attach --daemon` (add `--bootstrap` or `--replace` when
40
42
  the page requires it). An empty local registry is valid if you only want to
@@ -47,8 +49,13 @@ from actions that read or mutate a selected resource.
47
49
 
48
50
  Actions execute in the user's signed-in browser session.
49
51
 
50
- - Use the same internal APIs as the product page, with
51
- `credentials: "include"`.
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.
52
59
  - Never guess endpoints or schemas. Capture at least one real 200 before
53
60
  implementing a parser. Do not implement a path that was not observed.
54
61
  - Verify that the user is signed in before observing a workflow.
@@ -56,7 +63,8 @@ Actions execute in the user's signed-in browser session.
56
63
  ask the user to sign in.
57
64
  - Validate parameters and return sanitized errors. Return **data only** — never
58
65
  `directive`, `instructions`, or a paragraph telling the copilot what to say.
59
- Action and agent instructions live in Agent Studio / MCP.
66
+ Action and agent instructions live in Agent Studio / MCP. Never return
67
+ `ApiResult.detail` from `execute` — it is for the console.
60
68
  - Actions that return data the user will inspect should include a `render`
61
69
  function for in-chat UI (a chatterblock). If it makes more sense to output the data
62
70
  in a UI component instead of text then consider using render to show a component.
@@ -159,13 +167,40 @@ Do not report success without all six:
159
167
  - `agent/actions/` — one handler per action (`execute`, optional `render`),
160
168
  registered in `index.ts`
161
169
  - `agent/api/` — one HTTP helper per endpoint
162
- - `agent/constants.ts` — agent, product, and domain identifiers
163
- - `agent/utils.ts` Foldspace agent lookup
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.**
164
174
  - `foldspace.dev.json` — local harness target configuration
165
175
  - `docs/app-profile.md` — what is known about this app, and how it was established
166
176
 
167
177
  Do not introduce another bundler or bundle format.
168
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
+
169
204
  ## Agent learnings
170
205
 
171
206
  `docs/app-profile.md` is the durable record of this app. Fill it as you probe,
@@ -1,33 +1,28 @@
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
+ //
4
+ // Import helpers from ../utils, not from @foldspace_npm/harness/runtime.
5
+ // The path below is a placeholder until you capture a real HTTP 200.
6
+
7
+ import { apiFetch, rankBy } from "../utils";
8
+
9
+ type Item = { id: string; name: string };
3
10
 
4
11
  export const example_action = {
5
- execute: async (params: { message?: string }) => {
6
- const message = typeof params?.message === "string" ? params.message.trim() : "";
7
- if (!message) {
8
- return { ok: false, error: "message is required" };
9
- }
12
+ execute: async (params: { query?: string }) => {
13
+ const query = typeof params?.query === "string" ? params.query.trim() : "";
10
14
 
11
- try {
12
- return { ok: true, echo: message };
13
- } catch (error) {
14
- const detail = error instanceof Error ? error.message : "unknown error";
15
- return { ok: false, error: detail };
15
+ const result = await apiFetch<{ items?: Item[] }>("/__observe_me");
16
+ if (!result.ok) {
17
+ return { ok: false, error: result.error };
16
18
  }
17
- },
18
19
 
19
- // Optional chatterblock: uncomment to render in-chat UI instead of
20
- // returning text-only data. See https://docs.foldspace.ai/guides/in-chat-ui/ for more information. Set
21
- // awaitUserInput: true for forms and confirmations.
22
- //
23
- // render: (data, host, header, callback, cancel) => {
24
- // host.replaceChildren();
25
- // if (data?.ok === false) {
26
- // host.textContent = data.error;
27
- // return;
28
- // }
29
- // const card = document.createElement("div");
30
- // card.textContent = data.echo;
31
- // host.append(card);
32
- // },
20
+ const items = result.data.items ?? [];
21
+ const matches = rankBy(items, query, {
22
+ searchKeys: ["name"],
23
+ idOf: (item) => item.id,
24
+ });
25
+
26
+ return { ok: true, matches };
27
+ },
33
28
  };
@@ -3,8 +3,16 @@
3
3
  // Registration here is the switch: the SDK transmits these to the server, and
4
4
  // the server excludes any active action it does not receive. Comment an entry
5
5
  // out and the copilot can no longer see it — no unpublishing required.
6
+ //
7
+ // LOAD_MODE "injected" publishes the registry for attach to swap. "embedded"
8
+ // skips that assignment because the host page already loads Foldspace.
9
+
10
+ import { LOAD_MODE } from "../constants";
6
11
 
7
12
  const actions = {};
8
- (window as any).__FOLDSPACE_REMOTE_ACTIONS__ = actions;
13
+
14
+ if (LOAD_MODE === "injected") {
15
+ (window as any).__FOLDSPACE_REMOTE_ACTIONS__ = actions;
16
+ }
9
17
 
10
18
  export default actions;
@@ -1,3 +1,28 @@
1
1
  export const AGENT_API_NAME = {{AGENT_API_NAME_JSON}};
2
2
  export const PRODUCT_ID = {{PRODUCT_ID_JSON}};
3
3
  export const APP_DOMAIN = {{APP_DOMAIN_JSON}};
4
+
5
+ /**
6
+ * Empty until you capture the app's own XHR. A guessed host looks like it
7
+ * worked. Include an explicit port if the app uses one.
8
+ */
9
+ export const API_BASE = "";
10
+
11
+ /**
12
+ * Set after observing where the session token lives, for example
13
+ * `{ kind: "localStorage", name: "access_token" }` or
14
+ * `{ kind: "cookie", name: "session" }`.
15
+ *
16
+ * Custom headers that are not Bearer belong in a local `apiFetch` override —
17
+ * see CLAUDE.md. Do not guess this value.
18
+ */
19
+ export const AUTH_SOURCE:
20
+ | { kind: "localStorage" | "cookie"; name: string }
21
+ | undefined = undefined;
22
+
23
+ /**
24
+ * "injected" assigns `window.__FOLDSPACE_REMOTE_ACTIONS__` so attach can swap
25
+ * the bundle. "embedded" does not — the page already publishes handlers
26
+ * through its own SDK snippet.
27
+ */
28
+ export const LOAD_MODE: "injected" | "embedded" = "injected";
@@ -1,15 +1,62 @@
1
- import { AGENT_API_NAME } from "./constants";
1
+ /**
2
+ * The seam between this repo and the harness page runtime.
3
+ *
4
+ * Actions import helpers from `../utils` (or `./utils`), never from
5
+ * `@foldspace_npm/harness/runtime` directly. Swapping an implementation —
6
+ * custom auth headers, a different `apiFetch`, branded loading UI — is a
7
+ * change to this file and nothing else. `configure` runs once at module load
8
+ * so `apiFetch` / `getAgent` can read tenant facts without importing
9
+ * `constants.ts` from the published package.
10
+ *
11
+ * ## What `export *` gives you
12
+ *
13
+ * HTTP (customer API, from the signed-in page):
14
+ * - `apiFetch` — JSON + `Authorization: Bearer`. Stop re-exporting this if
15
+ * the live app uses cookies, custom headers, or no Bearer (see below).
16
+ * - `apiFetchBinary` — same auth, raw bytes (file download).
17
+ * - `publicFetch` — no token; marketing / public origin only.
18
+ * - `getAuthToken` / `parseJwt` / `redact` — session read and safe logging.
19
+ * - `mapWithConcurrency` — cap parallel fetches over a list.
20
+ *
21
+ * Foldspace SDK (thin wrappers; the SDK is the source of truth):
22
+ * - `getAgent` — overlay handle for `searchInList` and similar.
23
+ * - `rankBy` — substring first, then SDK fuzzy match. Not for domain ranking.
24
+ * - `armAllInstances` — **attach/bootstrap only**, never from `execute`.
25
+ * Local `foldspace attach` already arms test mode. Importing this from an
26
+ * action can put those SDK calls in the production `dist` bundle.
27
+ *
28
+ * Widget chrome (unstyled; replace in `agent/views/` when brand matters):
29
+ * - `renderLoading` / `renderEmpty` / `renderError` / `renderFatal`
30
+ *
31
+ * Config (already called above; you rarely need these in an action):
32
+ * - `configure` / `getConfig`
33
+ *
34
+ * Leave `API_BASE` and `AUTH_SOURCE` empty in `constants.ts` until you capture
35
+ * a real XHR. Empty config makes `apiFetch` return `{ ok: false }` instead of
36
+ * guessing a host. Errors are returned, never thrown; show `result.error` in
37
+ * the widget and log `result.detail` only.
38
+ *
39
+ * ## Override example (custom auth — do not guess Bearer)
40
+ *
41
+ * ```ts
42
+ * import { configure, apiFetch as defaultApiFetch, ... } from "@foldspace_npm/harness/runtime";
43
+ * // After configure(...):
44
+ * export async function apiFetch<T>(path: string, init: RequestInit = {}) {
45
+ * // observe the live request first, then copy its headers here
46
+ * return defaultApiFetch<T>(path, { ...init, headers: { ... } });
47
+ * }
48
+ * export { getAgent, rankBy, renderEmpty, renderError, renderFatal, renderLoading };
49
+ * // Do not `export *` if that would re-export the default apiFetch.
50
+ * ```
51
+ */
2
52
 
3
- let agent: any | null = null;
53
+ import { configure } from "@foldspace_npm/harness/runtime";
54
+ import { AGENT_API_NAME, API_BASE, AUTH_SOURCE } from "./constants";
4
55
 
5
- export function getAgent(): any | null {
6
- if (agent) {
7
- return agent;
8
- }
56
+ configure({
57
+ agentApiName: AGENT_API_NAME,
58
+ apiBase: API_BASE,
59
+ authSource: AUTH_SOURCE,
60
+ });
9
61
 
10
- agent = (window as any).foldspace?.agent({
11
- apiName: AGENT_API_NAME,
12
- });
13
-
14
- return agent;
15
- }
62
+ export * from "@foldspace_npm/harness/runtime";
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "target": "ES2022",
4
+ "lib": ["ES2022", "DOM"],
4
5
  "module": "ESNext",
5
6
  "moduleResolution": "bundler",
6
7
  "esModuleInterop": true,