@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 +19 -6
- package/dist/index.js +20 -7
- package/dist/prompt/compiled/interfaces.md +4 -23
- package/dist/prompt/skills/files.md +7 -3
- package/dist/prompt/skills/jewels.md +10 -3
- package/package.json +1 -1
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
|
-
|
|
285
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
103
|
-
base: process.env.MS_ASSET_BASE_URL || '/',
|
|
104
|
-
// ...
|
|
105
|
-
});
|
|
106
|
-
```
|
|
100
|
+
base: process.env.MS_ASSET_BASE_URL || '/',
|
|
107
101
|
|
|
108
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
}
|