@mindstudio-ai/remy 0.1.280 → 0.1.282

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/dist/headless.js CHANGED
@@ -281,8 +281,19 @@ async function* streamChat(params) {
281
281
  }
282
282
  var MAX_RETRIES = 5;
283
283
  var INITIAL_BACKOFF_MS = 1e3;
284
- function isRetryableError(error) {
285
- return /Network error/i.test(error) || /HTTP 5\d\d/i.test(error) || /Stream stalled/i.test(error) || /overloaded/i.test(error) || /terminated/i.test(error) || // Anthropic fetches url-source image blocks server-side on every call;
284
+ var RETRYABLE_ERROR_CODES = /* @__PURE__ */ new Set([
285
+ "api_error",
286
+ // Anthropic's 500-equivalent ("The server had an error…")
287
+ "overloaded_error"
288
+ // Anthropic's 529-equivalent
289
+ ]);
290
+ function isRetryableError(error, code) {
291
+ if (code && RETRYABLE_ERROR_CODES.has(code)) {
292
+ return true;
293
+ }
294
+ return /Network error/i.test(error) || /HTTP 5\d\d/i.test(error) || /Stream stalled/i.test(error) || /overloaded/i.test(error) || /terminated/i.test(error) || // The API's friendly mapping of a provider 500 — belt-and-suspenders for
295
+ // pods that don't send a machine-readable code.
296
+ /Internal API error/i.test(error) || // Anthropic fetches url-source image blocks server-side on every call;
286
297
  // a transient failure anywhere in that chain (S3 signing edge, resize
287
298
  // proxy, their fetcher) surfaces as this message. The images themselves
288
299
  // are fine — the same URLs typically succeed on the next attempt.
@@ -297,7 +308,7 @@ async function* streamChatWithRetry(params, options) {
297
308
  let retryableFailure = false;
298
309
  for await (const event of streamChat(params)) {
299
310
  if (event.type === "error") {
300
- if (isRetryableError(event.error) && attempt < MAX_RETRIES - 1) {
311
+ if (isRetryableError(event.error, event.code) && attempt < MAX_RETRIES - 1) {
301
312
  options?.onRetry?.(attempt, event.error);
302
313
  retryableFailure = true;
303
314
  break;
@@ -2897,13 +2908,13 @@ var lspDiagnosticsTool = {
2897
2908
  var restartProcessTool = {
2898
2909
  definition: {
2899
2910
  name: "restartProcess",
2900
- description: "Restart a managed sandbox process. Use this after running npm install or changing package.json to restart the dev server so it picks up new dependencies.",
2911
+ description: 'Restart a managed sandbox process. "devServer": after running npm install or changing package.json in the app frontend, so the dev server picks up new dependencies. "methodsWorker": after upgrading @mindstudio-ai/agent in the methods package \u2014 the long-lived methods worker caches the SDK module and will not pick up the new version otherwise (method source and other dependencies are always fresh; only an SDK upgrade needs this).',
2901
2912
  inputSchema: {
2902
2913
  type: "object",
2903
2914
  properties: {
2904
2915
  name: {
2905
2916
  type: "string",
2906
- description: 'Process name to restart. Currently supported: "devServer".'
2917
+ description: 'Process name to restart: "devServer" or "methodsWorker".'
2907
2918
  }
2908
2919
  },
2909
2920
  required: ["name"]
@@ -2912,7 +2923,9 @@ var restartProcessTool = {
2912
2923
  async execute(input) {
2913
2924
  const data = await lspRequest("/restart-process", { name: input.name });
2914
2925
  if (data.ok) {
2915
- await new Promise((resolve4) => setTimeout(resolve4, 5e3));
2926
+ if (input.name !== "methodsWorker") {
2927
+ await new Promise((resolve4) => setTimeout(resolve4, 5e3));
2928
+ }
2916
2929
  return `Restarted ${input.name}.`;
2917
2930
  }
2918
2931
  return `Error: unexpected response: ${JSON.stringify(data)}`;
package/dist/index.js CHANGED
@@ -262,8 +262,13 @@ async function* streamChat(params) {
262
262
  };
263
263
  }
264
264
  }
265
- function isRetryableError(error) {
266
- return /Network error/i.test(error) || /HTTP 5\d\d/i.test(error) || /Stream stalled/i.test(error) || /overloaded/i.test(error) || /terminated/i.test(error) || // Anthropic fetches url-source image blocks server-side on every call;
265
+ function isRetryableError(error, code) {
266
+ if (code && RETRYABLE_ERROR_CODES.has(code)) {
267
+ return true;
268
+ }
269
+ return /Network error/i.test(error) || /HTTP 5\d\d/i.test(error) || /Stream stalled/i.test(error) || /overloaded/i.test(error) || /terminated/i.test(error) || // The API's friendly mapping of a provider 500 — belt-and-suspenders for
270
+ // pods that don't send a machine-readable code.
271
+ /Internal API error/i.test(error) || // Anthropic fetches url-source image blocks server-side on every call;
267
272
  // a transient failure anywhere in that chain (S3 signing edge, resize
268
273
  // proxy, their fetcher) surfaces as this message. The images themselves
269
274
  // are fine — the same URLs typically succeed on the next attempt.
@@ -278,7 +283,7 @@ async function* streamChatWithRetry(params, options) {
278
283
  let retryableFailure = false;
279
284
  for await (const event of streamChat(params)) {
280
285
  if (event.type === "error") {
281
- if (isRetryableError(event.error) && attempt < MAX_RETRIES - 1) {
286
+ if (isRetryableError(event.error, event.code) && attempt < MAX_RETRIES - 1) {
282
287
  options?.onRetry?.(attempt, event.error);
283
288
  retryableFailure = true;
284
289
  break;
@@ -360,7 +365,7 @@ async function fetchRemyContext(config) {
360
365
  return null;
361
366
  }
362
367
  }
363
- var log, MAX_RETRIES, INITIAL_BACKOFF_MS, FALLBACK_ACK;
368
+ var log, MAX_RETRIES, INITIAL_BACKOFF_MS, RETRYABLE_ERROR_CODES, FALLBACK_ACK;
364
369
  var init_api = __esm({
365
370
  "src/api.ts"() {
366
371
  "use strict";
@@ -368,6 +373,12 @@ var init_api = __esm({
368
373
  log = createLogger("api");
369
374
  MAX_RETRIES = 5;
370
375
  INITIAL_BACKOFF_MS = 1e3;
376
+ RETRYABLE_ERROR_CODES = /* @__PURE__ */ new Set([
377
+ "api_error",
378
+ // Anthropic's 500-equivalent ("The server had an error…")
379
+ "overloaded_error"
380
+ // Anthropic's 529-equivalent
381
+ ]);
371
382
  FALLBACK_ACK = "[Message sent to agent. Agent is working in the background and will report back with its results when finished.]";
372
383
  }
373
384
  });
@@ -4044,13 +4055,13 @@ var init_restartProcess = __esm({
4044
4055
  restartProcessTool = {
4045
4056
  definition: {
4046
4057
  name: "restartProcess",
4047
- description: "Restart a managed sandbox process. Use this after running npm install or changing package.json to restart the dev server so it picks up new dependencies.",
4058
+ description: 'Restart a managed sandbox process. "devServer": after running npm install or changing package.json in the app frontend, so the dev server picks up new dependencies. "methodsWorker": after upgrading @mindstudio-ai/agent in the methods package \u2014 the long-lived methods worker caches the SDK module and will not pick up the new version otherwise (method source and other dependencies are always fresh; only an SDK upgrade needs this).',
4048
4059
  inputSchema: {
4049
4060
  type: "object",
4050
4061
  properties: {
4051
4062
  name: {
4052
4063
  type: "string",
4053
- description: 'Process name to restart. Currently supported: "devServer".'
4064
+ description: 'Process name to restart: "devServer" or "methodsWorker".'
4054
4065
  }
4055
4066
  },
4056
4067
  required: ["name"]
@@ -4059,7 +4070,9 @@ var init_restartProcess = __esm({
4059
4070
  async execute(input) {
4060
4071
  const data = await lspRequest("/restart-process", { name: input.name });
4061
4072
  if (data.ok) {
4062
- await new Promise((resolve4) => setTimeout(resolve4, 5e3));
4073
+ if (input.name !== "methodsWorker") {
4074
+ await new Promise((resolve4) => setTimeout(resolve4, 5e3));
4075
+ }
4063
4076
  return `Restarted ${input.name}.`;
4064
4077
  }
4065
4078
  return `Error: unexpected response: ${JSON.stringify(data)}`;
@@ -93,21 +93,13 @@ The project uses `"jsx": "react-jsx"` (automatic JSX transform) — do not `impo
93
93
 
94
94
  On deploy, the platform runs `npm install && npm run build` in the web directory and hosts the output on CDN.
95
95
 
96
- Two serving conventions every web interface must follow:
97
-
98
- 1. **Assets via `MS_ASSET_BASE_URL`.** The platform build sets this env var to a release-addressed CDN origin. Point the bundler's public path at it so asset URLs work on any host or mount path:
96
+ Two serving conventions: set the bundler's public path from `MS_ASSET_BASE_URL` (platform-set at build, unset locally), and take the router basename from `platform.basePath` — don't hardcode root-absolute URLs in app code.
99
97
 
100
98
  ```ts
101
99
  // vite.config.ts
102
- export default defineConfig({
103
- base: process.env.MS_ASSET_BASE_URL || '/',
104
- // ...
105
- });
106
- ```
100
+ base: process.env.MS_ASSET_BASE_URL || '/',
107
101
 
108
- 2. **Router basename from `platform.basePath`.** The platform injects the path prefix the page is served under ('' on the app's own hosts, the mount path when another app mounts this one). The SDK prefixes its own calls automatically; app code must feed it to the router and avoid hardcoded root-absolute URLs (`<img src="/logo.png">`, raw `location.assign('/about')`):
109
-
110
- ```ts
102
+ // router
111
103
  createBrowserRouter(routes, { basename: platform.basePath || '/' });
112
104
  ```
113
105
 
@@ -157,18 +149,7 @@ await prerender.invalidate(['/u/abc']); // omit arg to purge all
157
149
 
158
150
  ### Mounting other apps
159
151
 
160
- A web interface can serve other apps from the same workspace under path prefixes of its own hosts (the multi-zone pattern e.g. a marketing site serving a self-contained demo app at `/demos/vector-search` instead of linking to its subdomain):
161
-
162
- ```json
163
- { "web": { "mounts": [{ "path": "/demos/vector-search", "app": "vector-search-demo" }] } }
164
- ```
165
-
166
- - `path`: non-root mount prefix; mounts must be disjoint (none may prefix another).
167
- - `app`: the target's `custom_subdomain` or appId — a v2 app in the same workspace, validated at build.
168
-
169
- Under the mount everything is the child's, served first-class (its live bundle, session, backend, auth, telemetry, frame policy). The child needs no mount-specific config and keeps working at its own subdomain; deploys stay independent. Mount-safety = the two serving conventions above (`MS_ASSET_BASE_URL` assets + `platform.basePath` basename, no hardcoded root-absolute URLs); the parent's build log warns when a target's build isn't mount-safe.
170
-
171
- Caveats: prerendering for mounted paths is declared in the PARENT's `prerender.paths`; the auth cookie is per-host, so don't mount an auth-enabled app on a parent that also uses auth; a mount whose target has no live web build falls through to the parent's SPA.
152
+ 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) and needs no mount-specific config; the two serving conventions above are what make an app mountable.
172
153
 
173
154
  ## API Interface
174
155
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: Files, Storage & CDN
3
- what: Per-app blob storage the database doesn't model — user uploads, generated documents, marketing images. Stores are private by default (reads authed by the app session, or short-lived signed share links) or deliberately public — public files are CDN-served on the app's own domain and images resize on the fly via query params (width, height, crop, format, dpr), so the frontend requests the size it displays instead of CSS-scaling originals. User uploads go client-direct: the backend mints a token and the browser uploads straight to storage, bytes never pass through a method. SDK actions that produce files (generateImage, generatePdf, …) can write straight into a store, and the CLI uploads build-time assets (heroes, logos, OG images) so binaries never land in git. One store is shared across dev and prod on purpose.
3
+ what: Per-app blob storage the database doesn't model — user uploads, generated documents, marketing images. Stores are private by default (reads authed by the app session, or short-lived signed share links) or deliberately public — public files are CDN-served on the app's own domain with caching set per put via `cacheControl`, and images resize on the fly via query params (width, height, crop, format, dpr), so the frontend requests the size it displays instead of CSS-scaling originals. User uploads go client-direct: the backend mints a token and the browser uploads straight to storage, bytes never pass through a method. SDK actions that produce files (generateImage, generatePdf, …) can write straight into a store, and the CLI uploads build-time assets (heroes, logos, OG images) so binaries never land in git. One store is shared across dev and prod on purpose.
4
4
  when: Before defining a file store or writing any upload, download, share-link, or image-serving code — user uploads, generated documents, marketing assets, config blobs, anything durable the app stores outside the database.
5
5
  ---
6
6
 
@@ -45,7 +45,7 @@ const { files, cursor } = await Uploads.list({ prefix: 'reports/', limit: 100 })
45
45
  await Uploads.delete(key);
46
46
  ```
47
47
 
48
- - `put(content, { key?, contentType?, filename?, contentAddressed? })` → `StoredFile` (`{ key, url, size?, contentType?, updatedAt?, shareUrl() }`). Omit `key` → UUID; `contentAddressed: true` → a `<sha256>.<ext>` key (immutable/idempotent, for baked-in public assets).
48
+ - `put(content, { key?, contentType?, filename?, contentAddressed?, cacheControl? })` → `StoredFile` (`{ key, url, size?, contentType?, updatedAt?, shareUrl() }`). Omit `key` → UUID; `contentAddressed: true` → a `<sha256>.<ext>` key (immutable/idempotent, for baked-in public assets). `cacheControl` sets a public object's CDN caching — defaults: auto-minted keys (UUID / content-addressed, never reused) → immutable cache-forever; named keys → `public, max-age=300`.
49
49
  - **`file.url`** is a plain relative string — don't await it. To display a user *their own* file, hand them `file.url`; don't `get()` the bytes and stream them yourself.
50
50
  - **`await file.shareUrl({ expiresIn })`** → an absolute signed link that works with **no session** (email / cross-site embed). Private stores only; default 24h.
51
51
 
@@ -85,7 +85,11 @@ Public files are world-readable, served on the app's own domain, and **images re
85
85
 
86
86
  Combine freely: `…/hero.jpg?w=200&h=200&fit=crop&fm=avif`.
87
87
 
88
- Lightweight-config pattern: a public store + a stable `key` is a file the frontend can `fetch` with no DB hit and the backend can overwrite (`Config.put(json, { key: 'config/latest.json' })`).
88
+ ### How public caching works
89
+
90
+ A public `file.url` resolves through a short-lived redirect (cached ~5 min, stable target) to the CDN object, which is cached per its own `Cache-Control` — set at put time via `cacheControl`. Auto-minted keys (UUID / content-addressed / CLI uploads) are never reused, so they default to `public, max-age=31536000, immutable`; named (overwritable) keys default to `public, max-age=300`, so an overwrite is publicly visible within ~5 minutes. Tune per put: `'public, max-age=60'` for near-live data, `'no-store'` to revalidate every read, `immutable` for a named key you promise never to overwrite. Private files never edge-cache (reads are short-lived signed URLs).
91
+
92
+ Lightweight-config pattern: a public store + a stable `key` is a file the frontend can `fetch` with no DB hit and the backend can overwrite (`Config.put(json, { key: 'config/latest.json' })`). Overwrites propagate within ~5 minutes by default — pass `cacheControl` if the app needs tighter freshness.
89
93
 
90
94
  ## Build-time / marketing assets
91
95
 
@@ -116,7 +116,13 @@ export default defineJewel(categorizeRecord, {
116
116
  maxTurns: 6,
117
117
  });
118
118
  if (!task.output.category) return { input: null, reasoning: task.output.rationale };
119
- return { input: { recordId, category: task.output.category }, reasoning: task.output.rationale };
119
+ return {
120
+ input: { recordId, category: task.output.category },
121
+ reasoning: task.output.rationale,
122
+ // Preserves the model transcript with the pair: the training row,
123
+ // and at auto the audit trail.
124
+ trace: task.traceId,
125
+ };
120
126
  } catch (err) {
121
127
  // Couldn't produce conforming output; abstain with the evidence. Fail closed.
122
128
  return { input: null, reasoning: `Task agent failed: ${err instanceof Error ? err.message : String(err)}` };
@@ -137,6 +143,7 @@ To preserve signal integrity, the task prompt inside `propose` addresses the mod
137
143
  - **Context is plain imports plus tools.** Prefetch what every run needs (the row, its history, the inventory of valid values) into the task input; expose the app's own read methods as tools for what the agent should decide to look up (precedent, comparables). Tool calls are recorded with the pair.
138
144
  - **Use `runTask` with `outputSchema` for the judgment.** Build enums at runtime: a candidate-id enum means the agent structurally cannot reference a row that doesn't exist, and a known-values enum means it cannot invent a category. Put `null` in the enum so abstention is a first-class output rather than a formatting accident.
139
145
  - **Catch everything; abstain on failure.** A jewel that can't decide returns `{ input: null, reasoning }` with the evidence. It never throws to say "I don't know."
146
+ - **Attach the deciding task's trace.** `trace: task.traceId` on the success return preserves the full model transcript with the pair; use an array when the story has chapters (`trace: [first.traceId, escalation.traceId]`). Helper and formatting calls stay unattached; the attachment is the label for which run WAS the decision. A pair without a trace still grades, but it is thin as evidence and as training data.
140
147
  - **Propose a subset when that's the honest scope.** A jewel that never sets `assigneeId` and never proposes `closed` (it can't know a fix shipped) is expressing policy through its output shape.
141
148
  - **Reasoning is audit-log prose.** Two or three plain sentences a teammate would find useful; name the evidence. No headers, bullets, or emojis.
142
149
 
@@ -182,8 +189,8 @@ grade: async ({ proposed, actual }) => {
182
189
  });
183
190
  const c = rubric.output;
184
191
  return c.sameResolution && c.noContradiction
185
- ? { verdict: 'agree' }
186
- : { verdict: 'disagree', notes: `Failed: ${[!c.sameResolution && 'sameResolution', !c.noContradiction && 'noContradiction'].filter(Boolean).join(', ')}` };
192
+ ? { verdict: 'agree', trace: rubric.traceId }
193
+ : { verdict: 'disagree', notes: `Failed: ${[!c.sameResolution && 'sameResolution', !c.noContradiction && 'noContradiction'].filter(Boolean).join(', ')}`, trace: rubric.traceId };
187
194
  } catch (err) {
188
195
  return { verdict: 'skip', notes: `Judge failed: ${err instanceof Error ? err.message : String(err)}` };
189
196
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindstudio-ai/remy",
3
- "version": "0.1.280",
3
+ "version": "0.1.282",
4
4
  "description": "Remy coding agent",
5
5
  "repository": {
6
6
  "type": "git",