@mindstudio-ai/remy 0.1.304 → 0.1.306

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.
@@ -46,6 +46,7 @@ All fields are nested under the `"web"` key.
46
46
  | `prerender` | `object` | — | Opt into prerendering the listed routes/patterns for crawlers/unfurlers. See "Prerendering" below. |
47
47
  | `mounts` | `array` | — | Serve other same-workspace apps under path prefixes of this app's hosts. See "Mounting other apps" below. |
48
48
  | `redirects` | `array` | — | Path-level redirects. See "Redirects" below. |
49
+ | `rewrites` | `array` | — | Serve different content at the same URL. See "Rewrites" below. |
49
50
  | `trailingSlash` | `"strip"` \| `"append"` | — | Enforce a canonical trailing-slash form with a 308. Off by default. |
50
51
 
51
52
  ### Frontend SDK
@@ -168,11 +169,26 @@ Declare moved URLs in `web.json` — never ship a component that redirects clien
168
169
 
169
170
  `trailingSlash` picks the canonical form (`"strip"`: `/about/` → `/about`; `"append"`: the reverse, skipping the root and paths with file extensions). Off by default. Write `source` patterns without trailing slashes either way — normalization and matching resolve in one hop. Sources under `/_/` are rejected, as are self-referential and two-rule loops. A redirecting path is never prerendered, so don't list one in `prerender.paths`.
170
171
 
172
+ ### Rewrites
173
+
174
+ Serve different content at the same URL — no redirect. The way to give a clean path to a file that isn't in the build output:
175
+
176
+ ```json
177
+ { "web": { "rewrites": [
178
+ { "source": "/sitemap.xml", "destination": "/_/files/public/site/sitemap.xml" },
179
+ { "source": "/docs/*rest", "destination": "/help/*rest" }
180
+ ] } }
181
+ ```
182
+
183
+ `destination` is either `/_/files/public/<store>/<key>` (the object's bytes served inline — use this for anything the app generates at runtime and uploads: sitemaps, `robots.txt`, `llms.txt`, `.well-known/*`; the store must be public, private is rejected at build) or another path in the build output. Same `source` syntax as redirects, first match wins. Objects over 10 MB fall back to a CDN redirect. External URLs are not supported (use `mounts`), nor are other `/_/` surfaces.
184
+
185
+ Redirects resolve before everything; rewrites resolve at the content lookup, after prerendering — so a path with both gets the redirect.
186
+
171
187
  ### Mounting other apps
172
188
 
173
189
  Rare: `mounts` serves another same-workspace app under a path prefix of this app's hosts — `{ "web": { "mounts": [{ "path": "/docs", "app": "docs-site" }] } }` (`app` = the target's `custom_subdomain` or appId). The child is served first-class (its own bundle, session, backend, prerendering) and needs no mount-specific config; the two serving conventions above are what make an app mountable.
174
190
 
175
- Under a mount the child's own `web.json` governs its SEO and routing: its `prerender.paths` gates its mounted pages, its `prerender.invalidate` purges them, its `redirects`/`trailingSlash` apply within the prefix (written against its own paths, with the prefix added back to relative destinations), and its `sitemap.xml` is served with URLs rewritten to the mount and advertised in the parent's `robots.txt`. The child's `robots.txt` *rules* don't carry over (the build log lists them prefixed for the parent to adopt), and canonicals must be written from the page's own location a hardcoded absolute canonical points crawlers back at the child's host and undoes the mount.
191
+ Under a mount the child's own `web.json` governs its routing: its `prerender.paths` gates its mounted pages, its `prerender.invalidate` purges them, and its `redirects`/`rewrites`/`trailingSlash` apply within the prefix (written against its own paths). SEO files are NOT handled for you the child must generate its sitemap with the mounted URLs, and the parent must advertise it in its own `robots.txt` or sitemap index. Canonicals must be written from the page's own location; a hardcoded absolute canonical points crawlers back at the child's host and undoes the mount.
176
192
 
177
193
  ## API Interface
178
194
 
@@ -112,17 +112,29 @@ await chat.deleteThread(thread.id);
112
112
  await chat.claimThread(thread.id);
113
113
  ```
114
114
 
115
- **Client tools** — a tool whose effect happens in the browser (open a sheet, navigate, highlight) is declared with `target: "client"` and a `name` + inline `inputSchema` instead of a `method` (names must not collide with method ids; the schema is authored — there's no method contract to derive it from). The agent's invocation arrives as the `client_tool_call` stream event / the `onClientToolCall` callback on `sendMessage`; run the action there. Fire-and-forget on this surface: the agent is told the action was displayed and keeps going — the user's next message closes the loop.
115
+ **Client tools** — a tool whose effect happens in the browser (open a sheet, pick a file, confirm an action) is declared with `target: "client"` and a `name` + inline `inputSchema` instead of a `method` (names must not collide with method ids; the schema is authored — there's no method contract to derive it from). Register a handler and its **return value becomes the tool result**, so the agent learns what happened rather than assuming it did:
116
116
 
117
117
  ```js
118
- await chat.sendMessage(thread.id, text, {
119
- onText: (delta) => append(delta),
120
- onClientToolCall: (name, input) => {
121
- if (name === 'showVerification') openVerifySheet(input);
122
- },
118
+ chat.registerClientTool('pickFile', async ({ prompt }) => {
119
+ const file = await openFilePicker(prompt);
120
+ return file ? { path: file.path } : { cancelled: true };
123
121
  });
122
+
123
+ // Holding the turn on a person: the handler resolves when they decide.
124
+ chat.registerClientTool(
125
+ 'confirmDeploy',
126
+ ({ summary }) =>
127
+ new Promise((resolve) => {
128
+ showApprovalDialog(summary, {
129
+ onApprove: (note) => resolve({ approved: true, note }),
130
+ onReject: (reason) => resolve({ approved: false, reason }),
131
+ });
132
+ }),
133
+ );
124
134
  ```
125
135
 
136
+ The agent waits while the handler runs, up to 15 minutes — which is what makes confirm-before-acting a client tool rather than something you build a queue for. The SDK always answers, so the agent is never stuck: the return value, `{ error }` if the handler threw, `result_too_large` past ~32KB serialized, and `unhandled_client_tool` immediately when nothing is registered for that name; the platform supplies `client_timeout` if the window passes and `client_disconnected` if the page closes. Handlers live for the client's lifetime rather than one message. `onClientToolCall` on `sendMessage` behaves the same way for a one-off — whatever it returns is the result — and is consulted only when no handler is registered.
137
+
126
138
  **Sending messages (streaming):**
127
139
 
128
140
  `sendMessage` streams the agent's response via SSE. Use named callbacks for common events:
@@ -39,7 +39,7 @@ These are things we already know about and have decided to accept:
39
39
  - use [wouter](https://github.com/molefrog/wouter) for React routing instead of reaching for react-router
40
40
  - uploading user files should always happen via `platform.uploadFile()` from `@mindstudio-ai/interface` — not custom S3 code, not FormData to a method endpoint
41
41
  - for build-time prerendering of purely static sites (marketing pages with no dynamic content — distinct from the platform's crawler prerendering, below), roll your own with a post-build `renderToString` script — do not use `vite-prerender-plugin` (it bundles the prerender script as a client chunk, adding ~800KB to the user-facing bundle with no way to prevent it)
42
- - **Prerendering for crawlers/unfurlers is a platform feature — don't design around it.** For SEO / link-unfurl / AI-crawler visibility on a non-static SPA, the platform already handles it: routes opt in via `web.json` (`{ "web": { "prerender": { "paths": ["/blog/*"] } } }`), the SPA signals readiness by setting `data-prerender-ready` on the html element, deploys invalidate the snapshot cache automatically, and content that changes outside deploys is invalidated at runtime with `await prerender.invalidate([...])` from `@mindstudio-ai/agent` (called from the mutating method). It serves cached headless snapshots of the live SPA to bots — it is NOT build-time rendering. Do not recommend post-build render scripts, rebuild-on-content-change, or third-party prerender services for this. An app mounted under another app's path prefix keeps its own `prerender.paths` and its own `prerender.invalidate` — don't recommend moving prerender config to the parent, and don't hand-roll sitemap/robots rewriting for a mount (the platform rewrites the mounted sitemap and advertises it in the parent's robots.txt). The developer's main context carries the full interfaces reference with exact semantics — tell them to consult it rather than improvising.
42
+ - **Prerendering for crawlers/unfurlers is a platform feature — don't design around it.** For SEO / link-unfurl / AI-crawler visibility on a non-static SPA, the platform already handles it: routes opt in via `web.json` (`{ "web": { "prerender": { "paths": ["/blog/*"] } } }`), the SPA signals readiness by setting `data-prerender-ready` on the html element, deploys invalidate the snapshot cache automatically, and content that changes outside deploys is invalidated at runtime with `await prerender.invalidate([...])` from `@mindstudio-ai/agent` (called from the mutating method). It serves cached headless snapshots of the live SPA to bots — it is NOT build-time rendering. Do not recommend post-build render scripts, rebuild-on-content-change, or third-party prerender services for this. An app mounted under another app's path prefix keeps its own `prerender.paths` and its own `prerender.invalidate` — don't recommend moving prerender config to the parent. SEO files under a mount are NOT automatic: the child generates its sitemap with the mounted URLs (point its base-URL config at the mount) and the parent advertises it in its own robots.txt or sitemap index. To give a generated file a clean path (`/sitemap.xml`, `/robots.txt`, `/llms.txt`, `.well-known/*`) use a `web.json` rewrite to `/_/files/public/<store>/<key>` — never a redirect, which moves the URL, and never a client-side component. The developer's main context carries the full interfaces reference with exact semantics — tell them to consult it rather than improvising.
43
43
 
44
44
  ### Common pitfalls (always flag these)
45
45
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindstudio-ai/remy",
3
- "version": "0.1.304",
3
+ "version": "0.1.306",
4
4
  "description": "Remy coding agent",
5
5
  "repository": {
6
6
  "type": "git",