@mindstudio-ai/remy 0.1.308 → 0.1.310

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.
@@ -125,6 +125,8 @@ analytics.track('checkout_completed', { itemCount: 3, total: 47.99 });
125
125
 
126
126
  - **Apps can also READ their own analytics from backend methods** — the agent SDK's `analytics` namespace (lifetime per-page metrics, live visitor count, traffic sources, event stats), so an admin view can show real traffic next to the app's own data. Consult `askMindStudioSdk` for the query API when building one.
127
127
 
128
+ - **Live UI updates come from the events namespace, not polling** — backend code publishes to named channels (`events.publish`, agent SDK) and the page receives them instantly via `events.connect` (interface SDK). Live dashboards, notifications, chat, multi-tab sync. Load the `realtimeEvents` skill before designing with it.
129
+
128
130
  Analytics is **cookie-banner-free by design**: per-app scoping, IP discarded after geo lookup, country-level only, query strings server-scrubbed except for a UTM whitelist (`utm_*`, `ref`, `source`, `gclid`, `fbclid`, `msclkid`), no fingerprinting, no third-party scripts. If a user asks about GDPR cookie consent for analytics, you can explain why it is not needed.
129
131
 
130
132
  Disabling telemetry is a per-app dashboard setting (platform toggle, not code). Point users there if they ask.
@@ -64,7 +64,7 @@ my-app/
64
64
 
65
65
  Two things the platform cannot do — be upfront when a request heads this way:
66
66
  - Native mobile apps (iOS/Android). Mobile-responsive web apps are fine.
67
- - Real-time multiplayer with persistent connections (no WebSocket support). Turn-based or async multiplayer works great.
67
+ - Fast-twitch multiplayer and live co-editing (shared cursors, 60fps sync) — everything a client sends is a method invoke, so sub-100ms bidirectional interaction isn't a fit.
68
68
 
69
69
  ## The Two SDKs
70
70
 
@@ -154,3 +154,7 @@ Consider the ways in which AI can be incorporated into backend methods to solve
154
154
  ### Task Agents
155
155
 
156
156
  For multi-step tasks where the model needs to autonomously compose actions (research + scrape + generate, enrichment pipelines, content creation), use `runTask()` instead of chaining actions manually. It runs an agent loop and returns structured JSON. Its tools can include SDK actions, your app's own methods, and inline functions defined at the call site, so the agent can read your data to decide what to do next and write results back itself. Load the `taskAgents` skill before writing one — it is the full reference.
157
+
158
+ ### Realtime Events
159
+
160
+ Server→client push, no polling: `events.publish(channels, data)` from any method/cron/webhook reaches connected clients instantly; `events.grant(channels)` in a method (after your auth checks) mints the subscribe token the frontend's `events.connect` consumes. This is how live dashboards, notifications, chat, and multi-tab sync work — whenever you're about to write a frontend polling loop against your own backend, reach for this instead. Load the `realtimeEvents` skill before designing with it: channel shape (per-user fan-out vs broadcast) is the decision that matters, and the skill carries it.
@@ -65,6 +65,10 @@ Use MSFM annotations for implementation-level notes that the compiler needs but
65
65
 
66
66
  When defining tools for multi-user apps with access restrictions, be sure to note the roles that are allowed or disallowed from accessing the tool, as well as any other restrictions. The actual tool invocation will be rejected at runtime if the requesting user is not allowed to access the underlying method, but defining this early allows the model to gate permissions cleanly rather than vomiting an error when the user tries to do something they're not permissioned for.
67
67
 
68
+ ### Structured output rides on explicit control syntax
69
+
70
+ When the interface needs structured behavior inline — follow-up suggestion chips, a state flag the UI switches on, a directive to render an app component inside the answer — define an explicit syntax the agent emits (a fenced block, a marker line) and have the client parse that, rather than inferring intent from prose with regexes. Document the syntax in the compiled system prompt. A model-owned contract keeps rendering deterministic: the client knows exactly where each element begins and ends, which is also what makes buffered, finished-unit reveal possible.
71
+
68
72
  ### Anti-patterns
69
73
 
70
74
  - Avoid system prompts that restate tool schemas ("You have a tool called createTodo that takes a title and optional aiNotes...")
@@ -199,7 +203,9 @@ Display tokens as they arrive. No loading spinners that block the whole view —
199
203
 
200
204
  Use `streamdown` for rendering markdown from streaming text. It handles unterminated blocks gracefully (the core problem with react-markdown during mid-stream rendering), includes Shiki syntax highlighting for code blocks, and supports KaTeX math and Mermaid diagrams. Install the base package and tree-shake plugins as needed (`@streamdown/code`, `@streamdown/math`, `@streamdown/mermaid`).
201
205
 
202
- Pay attention to streaming text animation fast token delivery can look jarring, and slow delivery can look laggy. Throttling renders to ~50-100ms batches smooths things out.
206
+ One caveat: streamdown re-parses and re-renders the entire answer on every token, which remounts any component embedded in the markdown (an inline card, a citation popover) on every token — visible flicker, plus a flash of raw directive text. Keep the settled portion of the answer out of the re-parse path: split it at element boundaries into stable, memoized, keyed sibling nodes so only the actively streaming tail re-renders.
207
+
208
+ Stream plain prose live, but buffer anything structured — citation marks, code blocks, embedded components, any control syntax the agent emits — until its closing token arrives, then reveal it as a finished unit. The gate is the element boundary, not a timer: fixed-interval batching eventually splits an element mid-structure and the user watches raw syntax rewrite itself into the finished thing. The design expert's chat reference carries the full motion direction (reveal cadence, caret behavior); this is the mechanism that makes it implementable.
203
209
 
204
210
  It is critical to never introduce layout shift or jarring transitions when dealing with responses. Messages should cleanly and smoothly transition between thinking, streaming, and completed states. Tool use should fit beautifully within the conversation and should never cause abrupt layout shift.
205
211
 
@@ -0,0 +1,80 @@
1
+ ---
2
+ name: Realtime Events
3
+ what: Server→client push — backend code publishes to named channels and connected clients receive the payloads instantly over a platform-held stream, with no polling and no WebSocket code. This is how a dashboard updates the moment a cron finishes, a notification appears while the user is on another page, a chat message reaches every member of a room, and a second tab stays in sync with the first. Authorization is a grant minted by one of the app's own methods, so who-may-hear-what is ordinary backend code under the normal auth rules.
4
+ when: Before building anything that should update without a user action — live dashboards, notifications, chat, job/approval queues, multi-tab or multi-device sync, progress that outlives the method that started the work — or whenever you catch yourself writing a polling loop against your own backend.
5
+ ---
6
+
7
+ # Realtime Events
8
+
9
+ Three verbs. `events.publish(channels, data)` from any backend code (a method, a cron, a webhook handler). `events.grant(channels, opts?)` from a method, **after your own auth checks** — the grant is the entire subscribe-side authorization, so whoever holds it receives those channels. `events.connect(...)` in the frontend, which manages reconnection and grant renewal itself.
10
+
11
+ This is different from `stream()`, which narrates one invocation to the caller currently waiting on it. Events reach clients that weren't part of the invocation at all — someone else's action, a cron, a webhook.
12
+
13
+ ## The loop
14
+
15
+ ```ts
16
+ // backend — the subscribe door is one of YOUR methods
17
+ import { auth, events } from '@mindstudio-ai/agent';
18
+
19
+ export async function watchInbox() {
20
+ // Your auth checks first — the grant is the whole subscribe-side authorization.
21
+ auth.requireRole('member');
22
+ return await events.grant(`user:${auth.userId}`); // { token, expiresAt, ttlSeconds }
23
+ }
24
+
25
+ // backend — anything can publish (method, cron, webhook)
26
+ export async function assignTicket(input: { ticketId: string; assignee: string }) {
27
+ const ticket = await Tickets.update(input.ticketId, { assignee: input.assignee });
28
+ await events.publish(`user:${input.assignee}`, { type: 'ticket', id: ticket.id });
29
+ return ticket;
30
+ }
31
+ ```
32
+
33
+ ```ts
34
+ // frontend
35
+ import { createClient, events } from '@mindstudio-ai/interface';
36
+ const api = createClient();
37
+
38
+ const sub = events.connect({
39
+ getToken: () => api.watchInbox().then((r) => r.token),
40
+ onEvent: (e) => { if (e.data.type === 'ticket') refreshTicket(e.data.id); },
41
+ onConnect: () => refetchInbox(), // fires on EVERY (re)connect — see below
42
+ });
43
+ // sub.close() on unmount
44
+ ```
45
+
46
+ ## Channel shape — the design decision that matters
47
+
48
+ **A channel is an audience, and a method decides who is in it.** Never put two users' data on one channel; the channel is the unit of authorization. For anonymous visitors `auth.userId` is null — key their channels on `session.visitorId` instead, or `` `user:${auth.userId}` `` becomes `user:null`, one channel shared by every anonymous user.
49
+
50
+ **Default: per-user channels, fan out at publish time.** For any membership-gated audience — chat rooms, notifications, assigned work — publish to each member's own channel rather than to an entity's:
51
+
52
+ ```ts
53
+ const members = await RoomMembers.where({ roomId });
54
+ await events.publish(members.map((m) => `user:${m.userId}`), { type: 'message', roomId, msg });
55
+ ```
56
+
57
+ One call handles up to 500 channels. The subscriber's grant is one channel (`user:${id}`) minted once and never re-minted on a membership change, the client routes by payload (`roomId`), and removing someone from the room stops their events **immediately** — the publisher simply stops enumerating them.
58
+
59
+ **Exception: a shared channel for genuinely broadcast content** — a live blog, a status ticker, a public scoreboard — where everyone receives the same thing and access is broad by design. There, revocation waits for the grant TTL, which is fine.
60
+
61
+ The anti-pattern is a channel per entity (`room:${roomId}`) with users granted many channels: grants churn on every join/leave, and a removed member keeps receiving until their grant expires.
62
+
63
+ ## The contract
64
+
65
+ - **Events are nudges, at-most-once.** Nothing is buffered while a client is disconnected; nothing replays on connect. Subscribe for speed, **reconcile for truth**: `onConnect` fires on every (re)connect and is where you refetch current state. A subscriber without that refetch silently misses whatever happened while it was away.
66
+ - **Grant TTL is the revocation window** (default 15 min, max 1 h). The stream closes at expiry and the SDK re-mints through your method, re-running your checks — a user whose access you revoke keeps receiving for at most the TTL (or instantly, with per-user fan-out).
67
+ - **Environments never cross.** Publishes and grants are scoped live / preview / dev automatically — a tunnel-session publish cannot reach live users.
68
+ - **Exact channel strings.** No wildcards or prefixes exist. Names are letters, digits, and `: _ - .`; up to 500 channels per publish, 100 per grant.
69
+ - **Payloads are ids, not documents** — 32k serialized cap. Publish `{ type, id }`, let the client fetch.
70
+ - `publish` returns `{ delivered }` — live subscriber connections counted per channel. `0` means nobody is listening right now, which is normal for a nudge, never an error.
71
+
72
+ ## Debugging (`remy-admin events`)
73
+
74
+ - `events tail [--for 60]` — prints publishes live; "is my backend publishing what I think it is". Bounded, exits 0 on its own.
75
+ - `events channels list` — channels with recent publishes/subscriptions + live subscriber counts. A channel with subscribers but no publishes (or the reverse) means the two sides spell the channel differently.
76
+ - `events publish <channel> '<json>'` — verify a frontend subscriber before the backend trigger exists.
77
+
78
+ ## What this is not
79
+
80
+ No raw WebSockets and no client→client transport — everything upstream is a method invoke, with all its auth and logging. Sub-100ms bidirectional interaction (shared cursors, 60fps co-editing) is the wrong platform. No message history or replay — an app that needs "what did I miss" reads its own tables on connect, which the reconcile rule already requires.
@@ -34,7 +34,7 @@ An app can combine these freely. A monitoring tool might be cron jobs + a dashbo
34
34
 
35
35
  ### Not a Good Fit
36
36
 
37
- The Platform Limits (see the platform docs: no native mobile, no WebSocket realtime) apply here with extra force — surface them early if the conversation is heading that way, and steer toward what works (responsive web apps; turn-based or async multiplayer).
37
+ The Platform Limits (see the platform docs: no native mobile, no fast-twitch multiplayer or live co-editing) apply here with extra force — surface them early if the conversation is heading that way, and steer toward what works (e.g. responsive web apps rather than native mobile).
38
38
 
39
39
  ### Guiding the Conversation
40
40
 
@@ -26,18 +26,27 @@ Code blocks, tables, and lists inside agent turns are part of the brand too: sty
26
26
 
27
27
  ## Streaming craft
28
28
 
29
- Streaming is the heartbeat of the surface, and it must feel poured, not stuttered. Design the message lifecyclethinking streaming complete — as one set of continuous, layout-shift-free transitions:
29
+ Streaming is the heartbeat of the surface, and it must feel poured, not stuttered. The governing principle: the answer appears **written, not assembled** nothing that would leak the rendering mechanism is ever shown mid-formation. Plain prose streams live, token by token. Anything with structure a citation mark, a code block, an inline card, any control syntax the model emits — buffers until it is whole, then reveals as a finished unit. The gate is the element's closing boundary, not a timer: fixed batches eventually split an element mid-structure, and the user watches a naked bracket become a citation or a bare fence flicker into a code block. That reads as broken even when it is technically live.
30
+
31
+ Prescribe the reveal cadence as a drain, not a metronome. Pending text drains onto the screen on an animation-frame loop, revealing a fraction of whatever is pending each frame (18% per frame with a 3-character floor is the proven default), so a burst of tokens pours quickly and eases as it catches up, and a stalled model quiets the reveal on its own. A fixed characters-per-tick reveal reads as mechanical typing, not a voice.
32
+
33
+ The caret at the streaming edge is a status indicator, not decoration: solid while text is actively pouring, blinking only once the stream has stalled for ~400ms (so a blink genuinely means "waiting on the model"), fading out on completion rather than snapping away. It sits inline with the last text node — never dropped to its own line below a list, never floating after a code block.
34
+
35
+ Design the message lifecycle — thinking → streaming → complete — as one set of continuous, layout-shift-free transitions:
30
36
 
31
37
  - **Thinking** shows as a compact, in-character indicator the moment the user sends (the optimistic send is non-negotiable: the user's message appears instantly, the indicator with it). If the model emits visible reasoning, give it a collapsed-but-present treatment the user can expand — never a wall of gray text pushed above the answer.
32
- - **Streaming** renders batched (~50–100ms per paint, not per token) so arrival reads as pouring; give the streaming edge a treatment a soft cursor or shimmer so "still writing" is legible at a glance.
33
- - **Completion** is a settle, not a jump: the cursor fades, actions (copy, retry) ease in. No element of the transcript moves except by growing downward.
38
+ - **Completion** is a settle, not a jump: the caret fades, actions (copy, retry) ease in. No element of the transcript moves except by growing downward.
34
39
  - The transition between these states never reflows what's already on screen — reserve space for indicators instead of inserting them.
35
40
 
41
+ Entrances are choreographed. Suggested-prompt chips and any list of generated items enter with a per-item stagger — simultaneous appearance reads as a glitch. A number that changes on screen counts up in tabular figures rather than snapping. And every animated element gets an explicit reduced-motion path that shows the final state instantly while keeping the feature fully functional — reduced motion is designed, not bolted on.
42
+
36
43
  ## Tool activity
37
44
 
38
45
  When the agent calls the app's methods, the transcript should show it working the way the product would say it: a compact status row in the app's voice ("Booking your appointment…", "Searching your library…"), appearing when the call starts and resolving in place when it lands — never raw method names, spinners without labels, or JSON. Design the row as a real element of the transcript: aligned to the agent's column, quiet, layout-stable.
39
46
 
40
- Results deserve more than prose when they're structural: the record the agent pulled up, the item it created, the rows it found can render as real UI inside the turn — a card, a compact list, the app's own components — so the agent visibly operates the same product the user sees. Decide which tools earn a rendered result and prescribe what it looks like. When the agent runs several tools in a row, collapse them into one grouped status that expands on demand; a stack of six status rows reads as noise.
47
+ Results deserve more than prose when they're structural: the record the agent pulled up, the item it created, the rows it found can render as real UI inside the turn — a card, a compact list, the app's own components — so the agent visibly operates the same product the user sees. When the data behind the agent is structured, the strongest move is to quote that structure as live UI rather than paraphrase it into a sentence and a link. Decide which tools earn a rendered result and prescribe what it looks like. When the agent runs several tools in a row, collapse them into one grouped status that expands on demand; a stack of six status rows reads as noise.
48
+
49
+ A dense panel that is itself a workspace — telemetry, an inspector, a long structured result — belongs in a dismissible overlay, not inline in the transcript: inline reveals are for glanceable things, and a tall instrument shoves the conversation around. In that instrument register, density is the aesthetic — tighten it rather than giving it conversational whitespace.
41
50
 
42
51
  ## The composer
43
52
 
@@ -53,7 +62,7 @@ Thread history is real navigation, not an afterthought dropdown: design where pa
53
62
 
54
63
  You art-direct this surface end-to-end. The developer has a terrible sense of design and will fill any gap you leave with a default — and defaults are how a designed conversation decays into a generic chatbot. Deliver an implementation-ready specification:
55
64
 
56
- - **Exact values everywhere.** The agent column's measure, type sizes and line heights for body and markdown levels, chip radius and max-width, spacing between and within turns, indicator dimensions, streaming batch interval, transition durations and easings, all colors as hexes from the brand.
65
+ - **Exact values everywhere.** The agent column's measure, type sizes and line heights for body and markdown levels, chip radius and max-width, spacing between and within turns, indicator dimensions, the reveal drain fraction and caret stall threshold, transition durations and easings, all colors as hexes from the brand.
57
66
  - **The message lifecycle, state by state.** Sending / thinking / streaming / complete / error — what appears, where space is reserved, what animates — plus the tool-row lifecycle (start, running, resolved, grouped), so no transition is left to improvisation.
58
67
  - **One answer per question.** If you would accept either of two options, pick one and prescribe it. "Something like," "roughly," and "consider" are how implementations go generic; the only tolerances that exist are the ones you state numerically.
59
- - **A verification checklist.** End with the specific things to screenshot-check after implementation — no layout shift while streaming (capture mid-stream), tool rows appearing and resolving cleanly, the empty state, code blocks inside a turn, the mobile viewport with the keyboard up — so the developer can prove the direction landed rather than assume it did.
68
+ - **A verification checklist.** End with the specific things to screenshot-check after implementation — no layout shift while streaming (capture mid-stream), no structured element caught half-formed mid-stream, tool rows appearing and resolving cleanly, the empty state, code blocks inside a turn, the mobile viewport with the keyboard up — so the developer can prove the direction landed rather than assume it did.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindstudio-ai/remy",
3
- "version": "0.1.308",
3
+ "version": "0.1.310",
4
4
  "description": "Remy coding agent",
5
5
  "repository": {
6
6
  "type": "git",