@mindstudio-ai/remy 0.1.305 → 0.1.307

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
@@ -3009,7 +3009,7 @@ var runScenarioTool = {
3009
3009
  var runMethodTool = {
3010
3010
  definition: {
3011
3011
  name: "runMethod",
3012
- description: 'Run a method in the dev environment and return the result. Use for testing methods after writing or modifying them. Returns output, captured console output, errors with stack traces, and duration. If it fails, check .logs/tunnel.log or .logs/requests.ndjson for more details. Returns synchronously \u2014 no need to sleep before checking results.\n\nBy default methods run unauthenticated. If the method is auth-gated (calls `auth.requireRole()`, filters on `auth.userId`, etc.), pass `userId: "testUser"` to run as the default test user \u2014 no scenario setup required, no userId lookup.',
3012
+ description: 'Run a method in the dev environment and return the result. Use for testing methods after writing or modifying them. Returns output, captured console output, errors with stack traces, and duration. If it fails, check .logs/tunnel.log or .logs/requests.ndjson for more details. Returns synchronously \u2014 no need to sleep before checking results.\n\nBy default methods run unauthenticated. If the method is auth-gated (calls `auth.requireRole()`, filters on `auth.userId`, etc.), pass `userId: "testUser"` to run as the default test user \u2014 no scenario setup required, no userId lookup. For a method gated on `auth.requireRole("system")` \u2014 cron, webhook, and email work \u2014 pass `roles: ["system"]`; that works whether or not the app has auth.',
3013
3013
  inputSchema: {
3014
3014
  type: "object",
3015
3015
  properties: {
@@ -3028,7 +3028,7 @@ var runMethodTool = {
3028
3028
  roles: {
3029
3029
  type: "array",
3030
3030
  items: { type: "string" },
3031
- description: 'Optional. Role names for this request (e.g. ["admin"]). For auth-enabled apps, roles without a userId run as the dev test user holding exactly these roles \u2014 a real user row, so `auth.userId` and `requireRole` behave like production. For apps without auth, roles attach to an anonymous call. Applies to this call only.'
3031
+ description: 'Optional. Role names for this request (e.g. ["admin"]). Roles without a userId bind to the dev test user holding exactly these roles \u2014 a real user row, so `auth.userId` and `requireRole` behave like production. `["system"]` is the exception and needs no user: it runs as the platform system identity, the same one cron, webhook, and email invocations get, so a system-gated method is testable in an app with no auth configured. Any other role requires an `auth` block in mindstudio.json \u2014 without one the app has no users to hold a role, and the call is rejected saying so. Applies to this call only.'
3032
3032
  }
3033
3033
  },
3034
3034
  required: ["method"]
package/dist/index.js CHANGED
@@ -4200,7 +4200,7 @@ var init_runMethod = __esm({
4200
4200
  runMethodTool = {
4201
4201
  definition: {
4202
4202
  name: "runMethod",
4203
- description: 'Run a method in the dev environment and return the result. Use for testing methods after writing or modifying them. Returns output, captured console output, errors with stack traces, and duration. If it fails, check .logs/tunnel.log or .logs/requests.ndjson for more details. Returns synchronously \u2014 no need to sleep before checking results.\n\nBy default methods run unauthenticated. If the method is auth-gated (calls `auth.requireRole()`, filters on `auth.userId`, etc.), pass `userId: "testUser"` to run as the default test user \u2014 no scenario setup required, no userId lookup.',
4203
+ description: 'Run a method in the dev environment and return the result. Use for testing methods after writing or modifying them. Returns output, captured console output, errors with stack traces, and duration. If it fails, check .logs/tunnel.log or .logs/requests.ndjson for more details. Returns synchronously \u2014 no need to sleep before checking results.\n\nBy default methods run unauthenticated. If the method is auth-gated (calls `auth.requireRole()`, filters on `auth.userId`, etc.), pass `userId: "testUser"` to run as the default test user \u2014 no scenario setup required, no userId lookup. For a method gated on `auth.requireRole("system")` \u2014 cron, webhook, and email work \u2014 pass `roles: ["system"]`; that works whether or not the app has auth.',
4204
4204
  inputSchema: {
4205
4205
  type: "object",
4206
4206
  properties: {
@@ -4219,7 +4219,7 @@ var init_runMethod = __esm({
4219
4219
  roles: {
4220
4220
  type: "array",
4221
4221
  items: { type: "string" },
4222
- description: 'Optional. Role names for this request (e.g. ["admin"]). For auth-enabled apps, roles without a userId run as the dev test user holding exactly these roles \u2014 a real user row, so `auth.userId` and `requireRole` behave like production. For apps without auth, roles attach to an anonymous call. Applies to this call only.'
4222
+ description: 'Optional. Role names for this request (e.g. ["admin"]). Roles without a userId bind to the dev test user holding exactly these roles \u2014 a real user row, so `auth.userId` and `requireRole` behave like production. `["system"]` is the exception and needs no user: it runs as the platform system identity, the same one cron, webhook, and email invocations get, so a system-gated method is testable in an app with no auth configured. Any other role requires an `auth` block in mindstudio.json \u2014 without one the app has no users to hold a role, and the call is rejected saying so. Applies to this call only.'
4223
4223
  }
4224
4224
  },
4225
4225
  required: ["method"]
@@ -14,7 +14,7 @@ Guidance for designing conversational AI agents and their frontends. An agent in
14
14
 
15
15
  A good system prompt establishes who the agent is — personality, tone, judgment style, the kind of person they sound like. It doesn't enumerate every possible interaction or restate what tools already describe.
16
16
 
17
- Short and opinionated beats long and comprehensive. "Sounds like a sharp, organized friend — brief by default" gives the model more to work with than a page of behavioral rules. Define constraints through character, not checklists. Let the model's judgment work.
17
+ Short and opinionated beats long and comprehensive. "Sounds like a sharp, organized friend — brief by default" gives the model more to work with than a page of behavioral rules. Define constraints through character, not checklists. Let the model's judgment work. Start minimal and add rules only for behaviors that actually misfire once the user has tested it — the thread log is the feedback loop, and `remy-admin agent threads get` reads a conversation verbatim (see "The agent CLI" below).
18
18
 
19
19
  Three things every compiled system prompt should carry, on top of the character:
20
20
 
@@ -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:
@@ -201,7 +213,7 @@ When the user sends a message, add it to the conversation immediately — don't
201
213
 
202
214
  ### Tool calls
203
215
 
204
- Show tool activity in the chat as a compact, inline status that appears when `onToolCallStart` fires and resolves when `onToolCallResult` arrives. Never show raw JSON, tool IDs, or internal details — just a human-readable description of what's happening.
216
+ Show tool activity in the chat as a compact, inline status that appears when `onToolCallStart` fires and resolves when `onToolCallResult` arrives. Never show raw JSON, tool IDs, or internal details — just a human-readable description of what's happening. Tool calls should be interleaved into the conversation so they flow naturally as part of the agent's response.
205
217
 
206
218
  ### Input area
207
219
 
@@ -224,6 +236,21 @@ The chat UI uses the app's design system — colors, typography, voice from `@br
224
236
  - Avoid designs that look like dated messaging apps from 2015
225
237
  - Avoid robotic empty states ("Hello! I'm your AI assistant. How can I help you today?")
226
238
 
239
+ ## The agent CLI
240
+
241
+ The `remy-admin agent` family is the conversation log — every thread the deployed agent has had, and each one's full transcript:
242
+
243
+ ```bash
244
+ remy-admin agent threads list --limit 10 # newest activity first
245
+ remy-admin agent threads get <threadId> # full transcript: messages + tool calls
246
+ ```
247
+
248
+ Transcripts are how you iterate on an agent: after the user tests it, read `agent threads get` for what was actually said and which tools ran with which arguments — a tool the agent never reached for, one it called with the wrong shape, a reply that ignored the result — and fix the system prompt and tool descriptions from that evidence rather than guesses.
249
+
250
+ In the list, `toolErrorCount` and `hasTurnError` point at the conversations worth opening (a failed tool call; a turn that broke on a model error, rate limit, or credits instead of replying). `devSession` is true for your own test conversations through the dev tunnel, so you can tell them from real traffic.
251
+
252
+ In a transcript, each message keeps the stored conversation's own shape: `user` for a person's message and also for a tool result (which carries `toolCallId`), `assistant` for the agent (carrying `toolCalls` when it asked for tools). Every method tool call also carries a `requestId` — `remy-admin requests get <requestId>` opens that call's input, output, `console.log` output and error, which is how you get from "the agent said something wrong" to the method that gave it bad data. Client tools run in the browser, so they have no requestId.
253
+
227
254
  ---
228
255
 
229
256
  # The wiring
@@ -317,6 +317,8 @@ export async function regenerateCache(input: {}) {
317
317
 
318
318
  Web frontend calls (`/_/methods`), API interface calls (`/_/api`), and agent chat all run as the authenticated user — they don't get the system role unless the user has been explicitly assigned it. You can assign `system` to app users via the dashboard or SDK if they need to manually trigger these methods.
319
319
 
320
+ The system role is a platform-minted identity, not a row in the app's users table, so it does not require auth. A cron-only app with no login and no user table can gate a method on `requireRole('system')` and it works. To exercise one in dev, run it with `roles: ["system"]` — `runMethod` gives it the same identity a real platform trigger gets, so the gate stays in place while you test.
321
+
320
322
  ## Login Page Example
321
323
 
322
324
  ```tsx
@@ -461,7 +463,7 @@ Auth works the same in dev/preview as in production — real verification codes
461
463
 
462
464
  All other emails and phone numbers receive real codes. There is no dev-mode bypass, no fake code, and no way to skip verification. When testing auth flows in the preview, use one of the test bypasses above or a real email/phone. (These dev bypasses work in dev sessions only and exist for you — they're distinct from *test accounts*, the platform setting for giving external reviewers a fixed-code login that works in production; see *Restricting Who Can Sign Up*.)
463
465
 
464
- This test account is the dev's standing identity: the preview's sign-in helper auto-fills it, the editor's Roles column edits its roles, and a scenario's `roles` field assigns roles to it after seeding. The `runMethod` tool's `userId: "testUser"` shortcut resolves to this same dev-bypass identity (as does `roles` without a `userId`). The platform find-or-creates a real users-table row for it on first call and caches the row's UUID for the rest of the dev session. **`auth.userId` inside the method is that UUID — not the literal string `"testUser"`.** The user row already exists, so don't try to insert it. If you need the UUID to seed app-specific rows that reference it (profiles, preferences, foreign keys), read it from any method response or query the users table directly: `SELECT id FROM users WHERE email = 'remy@mindstudio.ai'` (or `phone = '+15555555555'` for SMS-auth apps).
466
+ This test account is the dev's standing identity: the preview's sign-in helper auto-fills it, the editor's Roles column edits its roles, and a scenario's `roles` field assigns roles to it after seeding. The `runMethod` tool's `userId: "testUser"` shortcut resolves to this same dev-bypass identity, as does `roles` without a `userId` (except `roles: ["system"]`, which is platform-minted and needs no user row — see *System Role*). The platform find-or-creates a real users-table row for it on first call and caches the row's UUID for the rest of the dev session. **`auth.userId` inside the method is that UUID — not the literal string `"testUser"`.** The user row already exists, so don't try to insert it. If you need the UUID to seed app-specific rows that reference it (profiles, preferences, foreign keys), read it from any method response or query the users table directly: `SELECT id FROM users WHERE email = 'remy@mindstudio.ai'` (or `phone = '+15555555555'` for SMS-auth apps).
465
467
 
466
468
  For **"Sign in with Remy"** apps (`auth.methods` is `["remy"]`, with no `email-code`/`sms-code`), `testUser` — and `setupBrowser`, and the editor's Roles column — resolve to **the developer's own delegated Remy identity**, not the `remy@mindstudio.ai` code-bypass user. `auth.userId` is still that user's real UUID, but the `remy@mindstudio.ai` email lookup above does not apply — read the UUID from a method response instead.
467
469
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindstudio-ai/remy",
3
- "version": "0.1.305",
3
+ "version": "0.1.307",
4
4
  "description": "Remy coding agent",
5
5
  "repository": {
6
6
  "type": "git",