@mindstudio-ai/remy 0.1.200 → 0.1.202

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/README.md CHANGED
@@ -308,14 +308,24 @@ Return the full conversation history.
308
308
 
309
309
  Messages with `hidden: true` were generated by `runCommand` actions and should not be displayed in the UI.
310
310
 
311
+ The response also includes `queuedMessages` — the current pending-queue snapshot (possibly empty) — so a client can render the queue immediately on connect/reconnect. Live changes after that arrive via `queue_changed`.
312
+
311
313
  #### `cancel`
312
314
 
313
- Cancel the current turn. The cancel command gets `completed(success:true)`. The in-flight message command (if any) gets its own `completed(success:false, error:"cancelled")`.
315
+ Cancel the current turn. The cancel command gets `completed(success:true)`; any queued messages are flushed and returned on it as `cancelledMessages`. The in-flight message command (if any) gets its own `completed(success:false, error:"cancelled")`.
314
316
 
315
317
  ```json
316
318
  {"action": "cancel", "requestId": "r3"}
317
319
  ```
318
320
 
321
+ #### `cancelQueued`
322
+
323
+ Cancel pending **queued** messages without touching the in-flight turn. Omit `id` to cancel all queued user messages, or pass `id` (the `requestId` of a queued message) to cancel just that one. Only user messages are cancellable — chained and background messages are part of a system chain and are never removed. Responds with `completed(success:true, cancelledQueued:[...])` listing what was removed; a `queue_changed` event also fires with the updated queue.
324
+
325
+ ```json
326
+ {"action": "cancelQueued", "requestId": "r6", "id": "r2"}
327
+ ```
328
+
319
329
  #### `clear`
320
330
 
321
331
  Clear conversation history and delete the session file.
@@ -342,6 +352,7 @@ Events are emitted as newline-delimited JSON. Command responses include `request
342
352
  |-------|--------|-------------|
343
353
  | `ready` | | Headless mode initialized, ready for input |
344
354
  | `session_restored` | `messageCount` | Previous session loaded |
355
+ | `queue_changed` | `queuedMessages` | Queue contents changed (any push/shift/drain/cancel). Carries the full current snapshot — an empty array when the queue drains. The live queue-state signal; for the initial snapshot on connect/reconnect, read `queuedMessages` from `get_history`. |
345
356
  | `stopping` | | Shutdown initiated |
346
357
  | `stopped` | | Shutdown complete |
347
358
 
@@ -68,14 +68,9 @@ declare class HeadlessSession {
68
68
  start(): Promise<void>;
69
69
  private shutdown;
70
70
  private emit;
71
- /**
72
- * Emit a `completed` event and mark completedEmitted. Includes
73
- * `queuedMessages` if the queue has items (sandbox uses this to know the
74
- * agent is still busy with pipeline work).
75
- */
71
+ /** Emit a `completed` event and mark completedEmitted. Queue state is
72
+ * surfaced separately via the `queue_changed` event, not on `completed`. */
76
73
  private emitCompleted;
77
- /** Returns `{ queuedMessages }` when the queue is non-empty, else empty object. */
78
- private queueFields;
79
74
  /** Dispatch a simple (non-streaming) command: call handler, emit response + completed. */
80
75
  private dispatchSimple;
81
76
  /** Persist sessionStats + queue snapshot to .remy-stats.json. */
@@ -138,6 +133,13 @@ declare class HeadlessSession {
138
133
  private handleChangeModels;
139
134
  /** Cancel the running turn and drain the queue. Returns the drained items. */
140
135
  private handleCancel;
136
+ /**
137
+ * Remove pending queued messages — all user messages, or one by id.
138
+ * Only `source: 'user'` items are removable; chained and background
139
+ * messages are part of a system chain and are never cancellable. Does
140
+ * not affect the in-flight turn (use `cancel` for that).
141
+ */
142
+ private handleCancelQueued;
141
143
  private handleStdinLine;
142
144
  }
143
145
 
package/dist/headless.js CHANGED
@@ -313,6 +313,10 @@ Current date: ${now}
313
313
  {{compiled/agent-interfaces.md}}
314
314
  </building_agent_interfaces>
315
315
 
316
+ <building_mcp_interfaces>
317
+ {{compiled/mcp-interfaces.md}}
318
+ </building_mcp_interfaces>
319
+
316
320
  <media_cdn>
317
321
  {{compiled/media-cdn.md}}
318
322
  </media_cdn>
@@ -4693,7 +4697,7 @@ This is a capable, stable platform used in production by 100k+ users. Build with
4693
4697
  - Full-stack web apps \u2014 social platforms, membership sites, marketplaces, booking systems, community hubs \u2014 multi-user apps with auth, data, UI
4694
4698
  - Automations with no UI \u2014 cron jobs, webhook handlers, email processors, data sync pipelines
4695
4699
  - Marketing & launch pages \u2014 landing pages, waitlist pages with referral mechanics, product sites with scroll animations
4696
- - Bots \u2014 Discord slash-command bots, Telegram bots, MCP tool servers for AI assistants
4700
+ - Agent tools \u2014 MCP tool servers for AI assistants
4697
4701
  - Creative/interactive projects \u2014 browser games with p5.js or Three.js, interactive visualizations, generative art, portfolio sites
4698
4702
  - API services \u2014 backend logic exposed as REST endpoints
4699
4703
  - Simple static sites \u2014 no backend needed, just a web interface with a build step
@@ -4708,8 +4712,6 @@ Each interface type invokes the same backend methods. Methods don't know which i
4708
4712
  - API \u2014 auto-generated REST endpoints for every method
4709
4713
  - Cron \u2014 scheduled jobs on a configurable interval
4710
4714
  - Webhook \u2014 HTTP endpoints that trigger methods
4711
- - Discord \u2014 slash-command bots
4712
- - Telegram \u2014 message-handling bots
4713
4715
  - Email \u2014 inbound email processing
4714
4716
  - MCP \u2014 tool servers for AI assistants
4715
4717
  - Agent \u2014 conversational LLM interface with tool access to backend methods
@@ -7248,6 +7250,24 @@ var MessageQueue = class {
7248
7250
  this.onChange?.();
7249
7251
  return all;
7250
7252
  }
7253
+ /**
7254
+ * Remove all items matching `predicate`. Fires onChange only if something
7255
+ * was removed. Returns the removed items.
7256
+ */
7257
+ removeWhere(predicate) {
7258
+ const removed = [];
7259
+ this.items = this.items.filter((item) => {
7260
+ if (predicate(item)) {
7261
+ removed.push(item);
7262
+ return false;
7263
+ }
7264
+ return true;
7265
+ });
7266
+ if (removed.length > 0) {
7267
+ this.onChange?.();
7268
+ }
7269
+ return removed;
7270
+ }
7251
7271
  /** Copy of current queue contents (for surfacing on events). */
7252
7272
  snapshot() {
7253
7273
  return [...this.items];
@@ -7298,6 +7318,24 @@ function resolveAction(text) {
7298
7318
  next
7299
7319
  };
7300
7320
  }
7321
+ function getActionChain(startName) {
7322
+ const chain = [];
7323
+ const seen = /* @__PURE__ */ new Set();
7324
+ let name = startName;
7325
+ while (name && !seen.has(name)) {
7326
+ seen.add(name);
7327
+ chain.push(name);
7328
+ let body;
7329
+ try {
7330
+ body = readAsset("automatedActions", `${name}.md`);
7331
+ } catch {
7332
+ break;
7333
+ }
7334
+ const fm = body.match(/^---\s*\n([\s\S]*?)\n---/);
7335
+ name = fm?.[1].match(/^\s*next:\s*(\w+)\s*$/m)?.[1];
7336
+ }
7337
+ return chain;
7338
+ }
7301
7339
 
7302
7340
  // src/headless/index.ts
7303
7341
  var log14 = createLogger("headless");
@@ -7368,14 +7406,16 @@ var HeadlessSession = class {
7368
7406
  baseUrl: this.opts.baseUrl
7369
7407
  });
7370
7408
  const resumed = loadSession(this.state);
7371
- this.queue = new MessageQueue(loadQueue(), () => this.persistStats());
7409
+ this.queue = new MessageQueue(loadQueue(), () => {
7410
+ this.persistStats();
7411
+ this.emit("queue_changed", { queuedMessages: this.queue.snapshot() });
7412
+ });
7372
7413
  if (resumed) {
7373
7414
  this.emit("session_restored", {
7374
7415
  messageCount: this.state.messages.length,
7375
7416
  ...this.state.models && { models: this.state.models },
7376
7417
  modelSurfaces: MODEL_SURFACES,
7377
- allowedModelsByType: ALLOWED_MODELS_BY_TYPE,
7378
- ...this.queueFields()
7418
+ allowedModelsByType: ALLOWED_MODELS_BY_TYPE
7379
7419
  });
7380
7420
  }
7381
7421
  triggerBrandExtraction(
@@ -7425,7 +7465,7 @@ var HeadlessSession = class {
7425
7465
  });
7426
7466
  process.on("SIGTERM", this.shutdown);
7427
7467
  process.on("SIGINT", this.shutdown);
7428
- this.emit("ready", this.queueFields());
7468
+ this.emit("ready");
7429
7469
  }
7430
7470
  shutdown = () => {
7431
7471
  this.emit("stopping");
@@ -7449,19 +7489,12 @@ var HeadlessSession = class {
7449
7489
  }
7450
7490
  process.stdout.write(line);
7451
7491
  }
7452
- /**
7453
- * Emit a `completed` event and mark completedEmitted. Includes
7454
- * `queuedMessages` if the queue has items (sandbox uses this to know the
7455
- * agent is still busy with pipeline work).
7456
- */
7492
+ /** Emit a `completed` event and mark completedEmitted. Queue state is
7493
+ * surfaced separately via the `queue_changed` event, not on `completed`. */
7457
7494
  emitCompleted(rid, data) {
7458
- this.emit("completed", { ...data, ...this.queueFields() }, rid);
7495
+ this.emit("completed", { ...data }, rid);
7459
7496
  this.completedEmitted = true;
7460
7497
  }
7461
- /** Returns `{ queuedMessages }` when the queue is non-empty, else empty object. */
7462
- queueFields() {
7463
- return this.queue.length > 0 ? { queuedMessages: this.queue.snapshot() } : {};
7464
- }
7465
7498
  /** Dispatch a simple (non-streaming) command: call handler, emit response + completed. */
7466
7499
  dispatchSimple(requestId, eventName, handler) {
7467
7500
  try {
@@ -7639,11 +7672,7 @@ var HeadlessSession = class {
7639
7672
  });
7640
7673
  return;
7641
7674
  case "turn_cancelled": {
7642
- this.emit(
7643
- "completed",
7644
- { success: false, error: "cancelled", ...this.queueFields() },
7645
- rid
7646
- );
7675
+ this.emit("completed", { success: false, error: "cancelled" }, rid);
7647
7676
  this.completedEmitted = true;
7648
7677
  return;
7649
7678
  }
@@ -7767,7 +7796,7 @@ var HeadlessSession = class {
7767
7796
  * message — `running` stays held across the queue drain so no user
7768
7797
  * message can slip in mid-pipeline.
7769
7798
  */
7770
- async runSingleTurn(parsed, requestId) {
7799
+ async runSingleTurn(parsed, requestId, fromChain = false) {
7771
7800
  this.currentRequestId = requestId;
7772
7801
  this.currentAbort = new AbortController();
7773
7802
  this.completedEmitted = false;
@@ -7815,16 +7844,18 @@ var HeadlessSession = class {
7815
7844
  onboardingState,
7816
7845
  parsed.viewContext
7817
7846
  );
7818
- if (resolved?.next) {
7819
- this.queue.push({
7820
- command: {
7821
- action: "message",
7822
- text: sentinel(resolved.next),
7823
- onboardingState
7824
- },
7825
- source: "chain",
7826
- enqueuedAt: Date.now()
7827
- });
7847
+ if (resolved?.next && !fromChain) {
7848
+ for (const step of getActionChain(resolved.next)) {
7849
+ this.queue.push({
7850
+ command: {
7851
+ action: "message",
7852
+ text: sentinel(step),
7853
+ onboardingState
7854
+ },
7855
+ source: "chain",
7856
+ enqueuedAt: Date.now()
7857
+ });
7858
+ }
7828
7859
  }
7829
7860
  try {
7830
7861
  await runTurn({
@@ -7882,11 +7913,6 @@ var HeadlessSession = class {
7882
7913
  source: "user",
7883
7914
  enqueuedAt: Date.now()
7884
7915
  });
7885
- this.emit(
7886
- "queued",
7887
- { position: this.queue.length, ...this.queueFields() },
7888
- requestId
7889
- );
7890
7916
  return;
7891
7917
  }
7892
7918
  this.running = true;
@@ -7934,7 +7960,7 @@ var HeadlessSession = class {
7934
7960
  continue;
7935
7961
  }
7936
7962
  const nextRid = next.command.requestId ?? `${next.source}-${Date.now()}`;
7937
- await this.runSingleTurn(next.command, nextRid);
7963
+ await this.runSingleTurn(next.command, nextRid, next.source === "chain");
7938
7964
  }
7939
7965
  }
7940
7966
  /**
@@ -7998,6 +8024,17 @@ var HeadlessSession = class {
7998
8024
  }
7999
8025
  return this.queue.drain();
8000
8026
  }
8027
+ /**
8028
+ * Remove pending queued messages — all user messages, or one by id.
8029
+ * Only `source: 'user'` items are removable; chained and background
8030
+ * messages are part of a system chain and are never cancellable. Does
8031
+ * not affect the in-flight turn (use `cancel` for that).
8032
+ */
8033
+ handleCancelQueued(id) {
8034
+ return this.queue.removeWhere(
8035
+ (item) => item.source === "user" && (id === void 0 || item.command.requestId === id)
8036
+ );
8037
+ }
8001
8038
  //////////////////////////////////////////////////////////////////////////////
8002
8039
  // Stdin router
8003
8040
  //////////////////////////////////////////////////////////////////////////////
@@ -8062,7 +8099,11 @@ var HeadlessSession = class {
8062
8099
  ...this.state.models && { models: this.state.models },
8063
8100
  modelSurfaces: MODEL_SURFACES,
8064
8101
  allowedModelsByType: ALLOWED_MODELS_BY_TYPE,
8065
- ...this.queueFields()
8102
+ // Current queue snapshot for connect/reconnect — get_history is the
8103
+ // on-demand "current state" query. Always an array (possibly empty),
8104
+ // matching the queue_changed convention so the client reconciles the
8105
+ // same way from both; live mutations are pushed via queue_changed.
8106
+ queuedMessages: this.queue.snapshot()
8066
8107
  }));
8067
8108
  return;
8068
8109
  }
@@ -8106,6 +8147,16 @@ var HeadlessSession = class {
8106
8147
  );
8107
8148
  return;
8108
8149
  }
8150
+ if (action === "cancelQueued") {
8151
+ const id = parsed.id;
8152
+ const removed = this.handleCancelQueued(id);
8153
+ this.emit(
8154
+ "completed",
8155
+ { success: true, cancelledQueued: removed },
8156
+ requestId
8157
+ );
8158
+ return;
8159
+ }
8109
8160
  if (action === "stop_tool") {
8110
8161
  const id = parsed.id;
8111
8162
  const mode = parsed.mode ?? "hard";
package/dist/index.js CHANGED
@@ -1967,6 +1967,10 @@ Current date: ${now}
1967
1967
  {{compiled/agent-interfaces.md}}
1968
1968
  </building_agent_interfaces>
1969
1969
 
1970
+ <building_mcp_interfaces>
1971
+ {{compiled/mcp-interfaces.md}}
1972
+ </building_mcp_interfaces>
1973
+
1970
1974
  <media_cdn>
1971
1975
  {{compiled/media-cdn.md}}
1972
1976
  </media_cdn>
@@ -5474,7 +5478,7 @@ This is a capable, stable platform used in production by 100k+ users. Build with
5474
5478
  - Full-stack web apps \u2014 social platforms, membership sites, marketplaces, booking systems, community hubs \u2014 multi-user apps with auth, data, UI
5475
5479
  - Automations with no UI \u2014 cron jobs, webhook handlers, email processors, data sync pipelines
5476
5480
  - Marketing & launch pages \u2014 landing pages, waitlist pages with referral mechanics, product sites with scroll animations
5477
- - Bots \u2014 Discord slash-command bots, Telegram bots, MCP tool servers for AI assistants
5481
+ - Agent tools \u2014 MCP tool servers for AI assistants
5478
5482
  - Creative/interactive projects \u2014 browser games with p5.js or Three.js, interactive visualizations, generative art, portfolio sites
5479
5483
  - API services \u2014 backend logic exposed as REST endpoints
5480
5484
  - Simple static sites \u2014 no backend needed, just a web interface with a build step
@@ -5489,8 +5493,6 @@ Each interface type invokes the same backend methods. Methods don't know which i
5489
5493
  - API \u2014 auto-generated REST endpoints for every method
5490
5494
  - Cron \u2014 scheduled jobs on a configurable interval
5491
5495
  - Webhook \u2014 HTTP endpoints that trigger methods
5492
- - Discord \u2014 slash-command bots
5493
- - Telegram \u2014 message-handling bots
5494
5496
  - Email \u2014 inbound email processing
5495
5497
  - MCP \u2014 tool servers for AI assistants
5496
5498
  - Agent \u2014 conversational LLM interface with tool access to backend methods
@@ -8013,6 +8015,24 @@ var init_messageQueue = __esm({
8013
8015
  this.onChange?.();
8014
8016
  return all;
8015
8017
  }
8018
+ /**
8019
+ * Remove all items matching `predicate`. Fires onChange only if something
8020
+ * was removed. Returns the removed items.
8021
+ */
8022
+ removeWhere(predicate) {
8023
+ const removed = [];
8024
+ this.items = this.items.filter((item) => {
8025
+ if (predicate(item)) {
8026
+ removed.push(item);
8027
+ return false;
8028
+ }
8029
+ return true;
8030
+ });
8031
+ if (removed.length > 0) {
8032
+ this.onChange?.();
8033
+ }
8034
+ return removed;
8035
+ }
8016
8036
  /** Copy of current queue contents (for surfacing on events). */
8017
8037
  snapshot() {
8018
8038
  return [...this.items];
@@ -8064,6 +8084,24 @@ function resolveAction(text) {
8064
8084
  next
8065
8085
  };
8066
8086
  }
8087
+ function getActionChain(startName) {
8088
+ const chain = [];
8089
+ const seen = /* @__PURE__ */ new Set();
8090
+ let name = startName;
8091
+ while (name && !seen.has(name)) {
8092
+ seen.add(name);
8093
+ chain.push(name);
8094
+ let body;
8095
+ try {
8096
+ body = readAsset("automatedActions", `${name}.md`);
8097
+ } catch {
8098
+ break;
8099
+ }
8100
+ const fm = body.match(/^---\s*\n([\s\S]*?)\n---/);
8101
+ name = fm?.[1].match(/^\s*next:\s*(\w+)\s*$/m)?.[1];
8102
+ }
8103
+ return chain;
8104
+ }
8067
8105
  var NON_ACTION_SENTINELS;
8068
8106
  var init_resolve = __esm({
8069
8107
  "src/automatedActions/resolve.ts"() {
@@ -8168,14 +8206,16 @@ var init_headless = __esm({
8168
8206
  baseUrl: this.opts.baseUrl
8169
8207
  });
8170
8208
  const resumed = loadSession(this.state);
8171
- this.queue = new MessageQueue(loadQueue(), () => this.persistStats());
8209
+ this.queue = new MessageQueue(loadQueue(), () => {
8210
+ this.persistStats();
8211
+ this.emit("queue_changed", { queuedMessages: this.queue.snapshot() });
8212
+ });
8172
8213
  if (resumed) {
8173
8214
  this.emit("session_restored", {
8174
8215
  messageCount: this.state.messages.length,
8175
8216
  ...this.state.models && { models: this.state.models },
8176
8217
  modelSurfaces: MODEL_SURFACES,
8177
- allowedModelsByType: ALLOWED_MODELS_BY_TYPE,
8178
- ...this.queueFields()
8218
+ allowedModelsByType: ALLOWED_MODELS_BY_TYPE
8179
8219
  });
8180
8220
  }
8181
8221
  triggerBrandExtraction(
@@ -8225,7 +8265,7 @@ var init_headless = __esm({
8225
8265
  });
8226
8266
  process.on("SIGTERM", this.shutdown);
8227
8267
  process.on("SIGINT", this.shutdown);
8228
- this.emit("ready", this.queueFields());
8268
+ this.emit("ready");
8229
8269
  }
8230
8270
  shutdown = () => {
8231
8271
  this.emit("stopping");
@@ -8249,19 +8289,12 @@ var init_headless = __esm({
8249
8289
  }
8250
8290
  process.stdout.write(line);
8251
8291
  }
8252
- /**
8253
- * Emit a `completed` event and mark completedEmitted. Includes
8254
- * `queuedMessages` if the queue has items (sandbox uses this to know the
8255
- * agent is still busy with pipeline work).
8256
- */
8292
+ /** Emit a `completed` event and mark completedEmitted. Queue state is
8293
+ * surfaced separately via the `queue_changed` event, not on `completed`. */
8257
8294
  emitCompleted(rid, data) {
8258
- this.emit("completed", { ...data, ...this.queueFields() }, rid);
8295
+ this.emit("completed", { ...data }, rid);
8259
8296
  this.completedEmitted = true;
8260
8297
  }
8261
- /** Returns `{ queuedMessages }` when the queue is non-empty, else empty object. */
8262
- queueFields() {
8263
- return this.queue.length > 0 ? { queuedMessages: this.queue.snapshot() } : {};
8264
- }
8265
8298
  /** Dispatch a simple (non-streaming) command: call handler, emit response + completed. */
8266
8299
  dispatchSimple(requestId, eventName, handler) {
8267
8300
  try {
@@ -8439,11 +8472,7 @@ var init_headless = __esm({
8439
8472
  });
8440
8473
  return;
8441
8474
  case "turn_cancelled": {
8442
- this.emit(
8443
- "completed",
8444
- { success: false, error: "cancelled", ...this.queueFields() },
8445
- rid
8446
- );
8475
+ this.emit("completed", { success: false, error: "cancelled" }, rid);
8447
8476
  this.completedEmitted = true;
8448
8477
  return;
8449
8478
  }
@@ -8567,7 +8596,7 @@ var init_headless = __esm({
8567
8596
  * message — `running` stays held across the queue drain so no user
8568
8597
  * message can slip in mid-pipeline.
8569
8598
  */
8570
- async runSingleTurn(parsed, requestId) {
8599
+ async runSingleTurn(parsed, requestId, fromChain = false) {
8571
8600
  this.currentRequestId = requestId;
8572
8601
  this.currentAbort = new AbortController();
8573
8602
  this.completedEmitted = false;
@@ -8615,16 +8644,18 @@ var init_headless = __esm({
8615
8644
  onboardingState,
8616
8645
  parsed.viewContext
8617
8646
  );
8618
- if (resolved?.next) {
8619
- this.queue.push({
8620
- command: {
8621
- action: "message",
8622
- text: sentinel(resolved.next),
8623
- onboardingState
8624
- },
8625
- source: "chain",
8626
- enqueuedAt: Date.now()
8627
- });
8647
+ if (resolved?.next && !fromChain) {
8648
+ for (const step of getActionChain(resolved.next)) {
8649
+ this.queue.push({
8650
+ command: {
8651
+ action: "message",
8652
+ text: sentinel(step),
8653
+ onboardingState
8654
+ },
8655
+ source: "chain",
8656
+ enqueuedAt: Date.now()
8657
+ });
8658
+ }
8628
8659
  }
8629
8660
  try {
8630
8661
  await runTurn({
@@ -8682,11 +8713,6 @@ var init_headless = __esm({
8682
8713
  source: "user",
8683
8714
  enqueuedAt: Date.now()
8684
8715
  });
8685
- this.emit(
8686
- "queued",
8687
- { position: this.queue.length, ...this.queueFields() },
8688
- requestId
8689
- );
8690
8716
  return;
8691
8717
  }
8692
8718
  this.running = true;
@@ -8734,7 +8760,7 @@ var init_headless = __esm({
8734
8760
  continue;
8735
8761
  }
8736
8762
  const nextRid = next.command.requestId ?? `${next.source}-${Date.now()}`;
8737
- await this.runSingleTurn(next.command, nextRid);
8763
+ await this.runSingleTurn(next.command, nextRid, next.source === "chain");
8738
8764
  }
8739
8765
  }
8740
8766
  /**
@@ -8798,6 +8824,17 @@ var init_headless = __esm({
8798
8824
  }
8799
8825
  return this.queue.drain();
8800
8826
  }
8827
+ /**
8828
+ * Remove pending queued messages — all user messages, or one by id.
8829
+ * Only `source: 'user'` items are removable; chained and background
8830
+ * messages are part of a system chain and are never cancellable. Does
8831
+ * not affect the in-flight turn (use `cancel` for that).
8832
+ */
8833
+ handleCancelQueued(id) {
8834
+ return this.queue.removeWhere(
8835
+ (item) => item.source === "user" && (id === void 0 || item.command.requestId === id)
8836
+ );
8837
+ }
8801
8838
  //////////////////////////////////////////////////////////////////////////////
8802
8839
  // Stdin router
8803
8840
  //////////////////////////////////////////////////////////////////////////////
@@ -8862,7 +8899,11 @@ var init_headless = __esm({
8862
8899
  ...this.state.models && { models: this.state.models },
8863
8900
  modelSurfaces: MODEL_SURFACES,
8864
8901
  allowedModelsByType: ALLOWED_MODELS_BY_TYPE,
8865
- ...this.queueFields()
8902
+ // Current queue snapshot for connect/reconnect — get_history is the
8903
+ // on-demand "current state" query. Always an array (possibly empty),
8904
+ // matching the queue_changed convention so the client reconciles the
8905
+ // same way from both; live mutations are pushed via queue_changed.
8906
+ queuedMessages: this.queue.snapshot()
8866
8907
  }));
8867
8908
  return;
8868
8909
  }
@@ -8906,6 +8947,16 @@ var init_headless = __esm({
8906
8947
  );
8907
8948
  return;
8908
8949
  }
8950
+ if (action === "cancelQueued") {
8951
+ const id = parsed.id;
8952
+ const removed = this.handleCancelQueued(id);
8953
+ this.emit(
8954
+ "completed",
8955
+ { success: true, cancelledQueued: removed },
8956
+ requestId
8957
+ );
8958
+ return;
8959
+ }
8909
8960
  if (action === "stop_tool") {
8910
8961
  const id = parsed.id;
8911
8962
  const mode = parsed.mode ?? "hard";
@@ -228,7 +228,7 @@ Returns an array of user IDs with the specified role.
228
228
 
229
229
  ### System Role (Platform Triggers)
230
230
 
231
- When the platform invokes a method on behalf of the app (cron, webhook, email, Discord, Telegram), the execution runs as a system user with `auth.roles: ['system']`. Use `auth.requireRole('system')` to restrict methods to platform triggers only:
231
+ When the platform invokes a method on behalf of the app (cron, webhook, email), the execution runs as a system user with `auth.roles: ['system']`. Use `auth.requireRole('system')` to restrict methods to platform triggers only:
232
232
 
233
233
  ```typescript
234
234
  export async function regenerateCache(input: {}) {
@@ -35,7 +35,7 @@ The platform builds and deploys automatically:
35
35
 
36
36
  1. **Parse manifest** — read `mindstudio.json` from the commit
37
37
  2. **Compile methods** — esbuild bundles each method into a single JS file
38
- 3. **Compile interfaces** — build web SPA (`npm install && npm run build`), generate configs for API/Discord/Telegram/cron/etc.
38
+ 3. **Compile interfaces** — build web SPA (`npm install && npm run build`), generate configs for API/cron/webhook/etc.
39
39
  4. **Parse table schemas** — TypeScript AST to column definitions, diff against live database
40
40
  5. **Compute effects** — roles diff, cron diff, bot command diffs, table DDL
41
41
  6. **Apply** — create/update roles, sync bot commands, apply DDL to a staging database copy, swap the live pointer
@@ -2,7 +2,7 @@
2
2
 
3
3
  Interfaces are projections of the backend contract into different modalities. The same methods power all of them. An interface can be as complex and polished as you want, but it's always safe — the backend contract is where anything real happens. The interface can't break business logic or corrupt data.
4
4
 
5
- All external service connections (Discord bot tokens, Telegram bot setup, webhook secrets, email addresses) are configured at the project level by the user through the Remy platform. The agent's job is to write the config files and the methods that handle the requests — not to manage API keys, OAuth flows, or service registration.
5
+ All external service connections (webhook secrets, email addresses) are configured at the project level by the user through the Remy platform. The agent's job is to write the config files and the methods that handle the requests — not to manage API keys, OAuth flows, or service registration.
6
6
 
7
7
  ## Web Interface
8
8
 
@@ -243,53 +243,7 @@ Routes are mounted at `/_/api{path}` (e.g. `DELETE /_/api/vendors/abc123`).
243
243
 
244
244
  ## Platform-Triggered Interfaces
245
245
 
246
- Discord, Telegram, Cron, Webhook, and Email interfaces are invoked by the platform, not by a user session. Methods called through these interfaces run with `auth.roles: ['system']`. Use `auth.requireRole('system')` to restrict a method to platform triggers only.
247
-
248
- ## Discord Bot
249
-
250
- Slash commands that invoke methods.
251
-
252
- ### Config (`interface.json`)
253
-
254
- ```json
255
- {
256
- "discord": {
257
- "commands": [
258
- {
259
- "name": "submit-vendor",
260
- "description": "Request a new vendor",
261
- "method": "submit-vendor-request"
262
- }
263
- ],
264
- "loadingMessage": "Processing your request..."
265
- }
266
- }
267
- ```
268
-
269
- Commands are synced to Discord on deploy.
270
-
271
- ## Telegram Bot
272
-
273
- Bot commands and message handling.
274
-
275
- ### Config (`interface.json`)
276
-
277
- ```json
278
- {
279
- "telegram": {
280
- "commands": [
281
- {
282
- "command": "/submit",
283
- "description": "Submit a vendor request",
284
- "method": "submit-vendor-request"
285
- }
286
- ],
287
- "defaultMethod": "handle-message"
288
- }
289
- }
290
- ```
291
-
292
- `defaultMethod` handles free-text messages that don't match a command.
246
+ Cron, Webhook, and Email interfaces are invoked by the platform, not by a user session. Methods called through these interfaces run with `auth.roles: ['system']`. Use `auth.requireRole('system')` to restrict a method to platform triggers only.
293
247
 
294
248
  ## Cron
295
249
 
@@ -422,19 +376,156 @@ Methods invoked through this interface run with `auth.roles: ['system']` (see th
422
376
 
423
377
  ## MCP (Model Context Protocol)
424
378
 
425
- Expose methods as AI tools.
379
+ Expose the app to *external* AI agents — Claude Desktop, Cursor, other people's agents, anything that speaks MCP. Unlike the agent interface (which *is* an agent — its own LLM, personality, and chat UI), MCP has no model of its own; it's the app projected as an MCP server for an outside AI to drive.
380
+
381
+ It supports the full MCP surface:
382
+ - **Tools** — methods the agent can call (rich descriptions + machine-readable annotations).
383
+ - **Resources** — read-only app data the agent can pull into context, addressable by URI.
384
+ - **Prompts** — reusable, parameterized prompt templates the server offers.
385
+ - **Instructions** — server-level guidance shown to the calling agent (the toolset's "system prompt").
386
+
387
+ The platform hosts the server, handles auth like the API interface (optional — keyed or anonymous), and derives every tool's input schema from the method contract. Because the consumer is an external agent with no knowledge of your app, **the descriptions are the product** — see "Building MCP Interfaces" for how to write them.
388
+
389
+ ### Spec: `src/interfaces/mcp.md`
390
+
391
+ Frontmatter declares the server. The body's intro prose becomes the server `instructions`; `## Tools`, `## Resources`, and `## Prompts` sections declare the rest.
392
+
393
+ ```yaml
394
+ ---
395
+ name: Vendor Management
396
+ description: Tools and data for managing vendors and purchase orders.
397
+ type: interface/mcp
398
+ ---
399
+ ```
400
+
401
+ ```markdown
402
+ This server manages vendors and purchase orders. Read a vendor before updating it; submitted
403
+ requests go through approval before they become active.
404
+
405
+ ## Tools
406
+
407
+ ### Submit a vendor request
408
+ method: submit-vendor-request
409
+ ~~~
410
+ Submit a new vendor for approval. Use when the caller wants to add a vendor.
411
+ Do NOT use to modify an existing vendor — that's update-vendor.
412
+ - name: the vendor's legal name
413
+ - contactEmail: billing contact; required for approval routing
414
+ Returns the created vendor's id and its initial "pending" status.
415
+ ~~~
416
+
417
+ ### List vendors
418
+ method: list-vendors
419
+ annotations: readOnly
420
+ ~~~
421
+ List all vendors, newest first. Read-only.
422
+ ~~~
423
+
424
+ ## Resources
425
+
426
+ - list-vendors → app://vendors — "Vendors" — all vendors (application/json)
427
+ - get-vendor → app://vendors/{id} — "Vendor" — a single vendor by id (application/json)
428
+
429
+ ## Prompts
430
+
431
+ ### draft_vendor_email
432
+ description: Draft an outreach email to a vendor.
433
+ arguments: vendorId (required) — the vendor to contact
434
+ ~~~
435
+ Write a warm outreach email to vendor {{vendorId}} introducing our procurement process.
436
+ ~~~
437
+ ```
438
+
439
+ Don't hand-author input schemas — the platform derives them. For a resource template, `{param}` in the URI maps to the backing method's input.
440
+
441
+ ### Compiled Output: `dist/interfaces/mcp/`
442
+
443
+ ```
444
+ dist/interfaces/mcp/
445
+ ├── interface.json ← config the platform reads
446
+ ├── instructions.md ← server-level guidance (returned in `initialize`)
447
+ ├── tools/
448
+ │ ├── submitVendorRequest.md ← rich description, one per tool
449
+ │ └── listVendors.md
450
+ └── prompts/
451
+ └── draftVendorEmail.md ← prompt template body, one per prompt
452
+ ```
453
+
454
+ Resources carry inline metadata only — no per-resource file.
426
455
 
427
456
  ### Config (`interface.json`)
428
457
 
429
458
  ```json
430
459
  {
431
460
  "mcp": {
432
- "methods": ["submit-vendor-request", "list-vendors"]
461
+ "name": "Vendor Management",
462
+ "description": "Tools and data for managing vendors and purchase orders.",
463
+ "instructions": "instructions.md",
464
+ "tools": [
465
+ {
466
+ "method": "submit-vendor-request",
467
+ "name": "submit_vendor_request",
468
+ "title": "Submit Vendor Request",
469
+ "description": "tools/submitVendorRequest.md",
470
+ "annotations": { "readOnly": false, "destructive": false, "idempotent": false, "openWorld": false }
471
+ },
472
+ {
473
+ "method": "list-vendors",
474
+ "title": "List Vendors",
475
+ "description": "tools/listVendors.md",
476
+ "annotations": { "readOnly": true }
477
+ }
478
+ ],
479
+ "resources": [
480
+ { "method": "list-vendors", "uri": "app://vendors", "name": "Vendors", "description": "All vendors.", "mimeType": "application/json" },
481
+ { "method": "get-vendor", "uriTemplate": "app://vendors/{id}", "name": "Vendor", "description": "A single vendor by id.", "mimeType": "application/json" }
482
+ ],
483
+ "prompts": [
484
+ {
485
+ "name": "draft_vendor_email",
486
+ "title": "Draft vendor email",
487
+ "description": "Draft an outreach email to a vendor.",
488
+ "arguments": [ { "name": "vendorId", "description": "The vendor to contact", "required": true } ],
489
+ "template": "prompts/draftVendorEmail.md"
490
+ }
491
+ ]
433
492
  }
434
493
  }
435
494
  ```
436
495
 
437
- Each listed method becomes an MCP tool. Method names and descriptions from the manifest are used as tool names and descriptions.
496
+ | Field | Description |
497
+ |-------|-------------|
498
+ | `name`, `description` | Server display name + registry metadata (not shown to the calling agent) |
499
+ | `instructions` | Relative path to the server-level guidance returned in `initialize` |
500
+ | `tools[].method` | Method `id` from the manifest (kebab-case) |
501
+ | `tools[].name` | Tool name exposed to clients. Optional — defaults to the method `id`. Must match `[a-zA-Z0-9_-]` and be unique within the server |
502
+ | `tools[].title` | Optional human-friendly display name |
503
+ | `tools[].description` | Relative path to the tool's markdown description |
504
+ | `tools[].annotations` | Optional client hints (auto-call vs. confirm): `readOnly`, `destructive`, `idempotent`, `openWorld` — map to MCP's `readOnlyHint` etc. |
505
+ | `resources[].method` | The read method invoked when the resource is read |
506
+ | `resources[].uri` / `uriTemplate` | A static URI, or a template whose `{param}` maps to the method's input |
507
+ | `resources[].name`, `description`, `mimeType` | Resource metadata |
508
+ | `prompts[].name`, `title`, `description` | Prompt identity + metadata |
509
+ | `prompts[].arguments` | `[{ name, description?, required? }]` |
510
+ | `prompts[].template` | Relative path to the template body (`{{arg}}` placeholders) |
511
+
512
+ There is no `inputSchema` field — the platform derives each tool's schema from the method's input contract.
513
+
514
+ ### Platform Behavior
515
+
516
+ - The platform hosts the MCP server and exposes it to external clients. Clients connect at `POST https://{app-host}/_/mcp`.
517
+ - **Auth is optional**, identical to the API interface: a `Bearer` key resolves to a user with full RBAC; with no key, calls run anonymously (no user, no roles). The method is the boundary — gate sensitive tools with `auth.requireRole`/`requireUser`; a public (keyless) server exposes only the un-gated tools.
518
+ - Input schemas are derived automatically from each method's input contract.
519
+ - `tools/list` is static; access is enforced per-method at call time (a gated tool is listed but rejects an unauthorized call).
520
+ - A resource read invokes the backing method (template `{param}`s come from the URI) and returns its output as the resource contents.
521
+ - `prompts/get` fills the template with the provided arguments.
522
+ - `instructions` is returned in the `initialize` response.
523
+
524
+ ### Manifest
525
+
526
+ ```json
527
+ { "type": "mcp", "path": "dist/interfaces/mcp/interface.json" }
528
+ ```
438
529
 
439
530
  ## Agent (Conversational Interface)
440
531
 
@@ -514,8 +605,6 @@ Each interface is declared in `mindstudio.json`:
514
605
  { "type": "web", "path": "dist/interfaces/web/web.json" },
515
606
  { "type": "api" },
516
607
  { "type": "cron", "path": "dist/interfaces/cron/interface.json" },
517
- { "type": "discord", "path": "dist/interfaces/discord/interface.json" },
518
- { "type": "telegram", "path": "dist/interfaces/telegram/interface.json" },
519
608
  { "type": "webhook", "path": "dist/interfaces/webhook/interface.json" },
520
609
  { "type": "email", "path": "dist/interfaces/email/interface.json" },
521
610
  { "type": "mcp", "path": "dist/interfaces/mcp/interface.json" },
@@ -111,7 +111,7 @@
111
111
 
112
112
  | Field | Type | Required | Description |
113
113
  |-------|------|----------|-------------|
114
- | `type` | `string` | Yes | One of: `web`, `api`, `discord`, `telegram`, `cron`, `webhook`, `email`, `mcp`, `agent` |
114
+ | `type` | `string` | Yes | One of: `web`, `api`, `cron`, `webhook`, `email`, `mcp`, `agent` |
115
115
  | `path` | `string` | No | Path to the interface config file |
116
116
  | `config` | `object` | No | Inline config (alternative to a file) |
117
117
  | `enabled` | `boolean` | No | Default `true`. Set `false` to skip during build. |
@@ -0,0 +1,34 @@
1
+ # Building MCP Interfaces
2
+
3
+ Guidance for exposing an app as an MCP server — a tool / resource / prompt surface for *external* AI agents (Claude Desktop, Cursor, anyone's agent). The contract (spec format, compiled output, `interface.json`) is in the Interfaces doc; this is how to author one well. Unlike the agent interface, there's no LLM, personality, or UI to design — the entire product is the descriptions and the shape of what you expose.
4
+
5
+ ## The descriptions are the product
6
+
7
+ The calling agent is a stranger with no knowledge of your app. It decides what to invoke entirely from the names, descriptions, and annotations you ship. Follow the same principles as the agent interface's tool descriptions (see "Building Agent Interfaces" — when to use and when not, parameter guidance beyond the schema, what the tool returns) — but write them **self-contained**. An in-app agent tool can lean on the app's framing; an MCP tool can't, because the caller has no context. Spell out what an outsider wouldn't know.
8
+
9
+ ## Curate — not every method is a tool
10
+
11
+ Expose what an outside agent would actually use. Skip internal helpers, admin-only methods, and batch operations. A focused set of well-described tools beats a large set of thin ones. Note role restrictions in the description — gated tools are listed but reject unauthorized calls at runtime, so set expectations rather than surfacing a raw error.
12
+
13
+ ## Annotations
14
+
15
+ Annotations are machine-readable hints clients use to decide whether to auto-call a tool or ask the user first. Set them honestly:
16
+
17
+ - `readOnly` — the tool only reads, never mutates. The highest-value hint: clients auto-call reads without prompting, so set it on every pure read.
18
+ - `destructive` — the tool can delete or overwrite. Clients gate these behind confirmation.
19
+ - `idempotent` — calling twice with the same arguments has the same effect as calling once.
20
+ - `openWorld` — the tool reaches outside the app (external web/services) rather than operating only on app data.
21
+
22
+ ## Tools vs. resources
23
+
24
+ A **tool** is an action the agent *invokes*; a **resource** is data the agent *reads into context*. A read-only method can be either — expose it as a tool if the agent will call it as a step, as a resource if it's reference data the agent should pull in, and as both when both fit.
25
+
26
+ Resources are method-backed: a read invokes the method. Use a static `uri` for a fixed collection (`app://vendors`) and a `uriTemplate` when the read takes parameters (`app://vendors/{id}`, where `{id}` maps to the method's input). Keep URIs stable and human-legible.
27
+
28
+ ## Prompts
29
+
30
+ Prompts are reusable, parameterized templates the server offers to clients — e.g. a "draft a vendor email" starter. Author the template body with `{{arg}}` placeholders and declare its arguments. Offer a prompt when there's a recurring task worth packaging; skip it if a tool already covers the need.
31
+
32
+ ## Server instructions
33
+
34
+ The spec's intro prose becomes the server `instructions` — toolset-level guidance returned to the calling agent at connect time (its "system prompt"). Put *cross-cutting* guidance here: how the tools fit together, ordering or prerequisites ("read a vendor before updating it"), and norms that apply across the whole toolset. Keep per-tool specifics in the tool descriptions; instructions are for the toolset as a whole.
@@ -1,6 +1,6 @@
1
1
  # Methods
2
2
 
3
- A method is a named async function that runs on the platform. It's the universal unit of backend logic — every interface (web, API, Discord, cron, webhook) invokes methods. One file per method, one named export.
3
+ A method is a named async function that runs on the platform. It's the universal unit of backend logic — every interface (web, API, cron, webhook) invokes methods. One file per method, one named export.
4
4
 
5
5
  ## Writing a Method
6
6
 
@@ -41,12 +41,14 @@ my-app/
41
41
  package.json
42
42
  src/
43
43
  api/api.json REST API config
44
- discord/interface.json Discord bot config
45
- telegram/interface.json Telegram bot config
46
44
  cron/interface.json cron config
47
45
  webhook/interface.json webhook config
48
46
  email/interface.json email config
49
- mcp/interface.json MCP config
47
+ mcp/ MCP interface
48
+ interface.json MCP config
49
+ instructions.md server instructions
50
+ tools/ tool descriptions (one .md per method)
51
+ prompts/ prompt templates (one .md per prompt)
50
52
  agent/ agent interface
51
53
  agent.json agent config
52
54
  system.md compiled system prompt
@@ -93,7 +95,7 @@ const { vendor } = await api.approveVendor({ vendorId: '...' });
93
95
 
94
96
  - **Managed databases.** SQLite with typed schemas. Push a schema change and the platform diffs, migrates, and promotes atomically.
95
97
  - **Built-in auth.** Opt-in via manifest. Developer builds login UI, platform handles verification codes (email/SMS), cookie sessions, and role enforcement. Backend methods use `auth.requireRole('admin')` for access control.
96
- - **Multiple interfaces, one codebase.** Web, API, Discord, Telegram, Cron, Webhook, Email, MCP — all invoke the same methods. Methods don't know which interface called them.
98
+ - **Multiple interfaces, one codebase.** Web, API, Cron, Webhook, Email, MCP — all invoke the same methods. Methods don't know which interface called them.
97
99
  - **Sandboxed execution.** Each method invocation runs in its own isolated execution context with npm packages pre-installed.
98
100
  - **Git-native deployment.** Push to default branch to deploy. Push to feature branch for preview. Rollback is a git revert.
99
101
  - **Secrets.** Encrypted environment variables with separate dev/prod values. Injected as `process.env` in methods. For third-party service credentials not covered by the SDK.
@@ -11,7 +11,7 @@ Remy apps are full-stack TypeScript projects. You have a lot to work with:
11
11
  - **Backend (Methods):** TypeScript in a sandboxed runtime. Any npm package. Managed SQLite database with typed schemas and automatic migrations. Built-in app-managed auth with email/SMS verification, cookie sessions, and role enforcement. None of these are required — use what the app needs.
12
12
  - **Frontend (Web Interface):** Starts as Vite + React, but any TypeScript project with a build command works. Any framework, any library, or no framework at all.
13
13
  - **AI & integrations:** The `@mindstudio-ai/agent` SDK gives access to 200+ AI models (OpenAI, Anthropic, Google, Meta, Mistral, and more) and 1000+ integrations (email, SMS, Slack, HubSpot, Google Workspace, web scraping, image/video generation, media processing) with zero configuration — credentials are handled automatically. No API keys needed. Beyond individual actions, `runTask()` lets you spin up lightweight autonomous task agents that chain these actions together with judgment — e.g., a user types a restaurant name and the backend autonomously researches it in the background, finds the address, and generates a custom illustration. Think about where this kind of enrichment would make a feature go from functional to magical.
14
- - **Interfaces:** Web UI, REST API, cron jobs, webhooks, Discord bots, Telegram bots, MCP tool servers, email processors, conversational AI agents — all backed by the same methods. An app can use any combination.
14
+ - **Interfaces:** Web UI, REST API, cron jobs, webhooks, MCP tool servers, email processors, conversational AI agents — all backed by the same methods. An app can use any combination.
15
15
 
16
16
  This is a capable, stable platform. Build with confidence; you're building production-grade apps, not fragile prototypes.
17
17
 
@@ -24,7 +24,7 @@ Don't recite this list to users. Use it to calibrate your sense of what's possib
24
24
  - **Full-stack web apps** — social platforms, membership sites, marketplaces, booking systems, community hubs — multi-user apps with auth, data, UI
25
25
  - **Automations** — cron jobs that monitor competitors and send alerts, webhook handlers that sync data between services, email processors that triage support requests — no UI needed
26
26
  - **Conversational AI agents** — custom chat UIs backed by any model, with tool access to the app's methods. Full control over what the agent can do and who can use it
27
- - **Bots & agent tools** — Discord slash-command bots, Telegram bots, MCP tool servers for AI assistants
27
+ - **Agent tools** — MCP tool servers for AI assistants
28
28
  - **Creative projects** — browser games with p5.js or Three.js, interactive visualizations, 3D things, generative art, portfolio sites with dynamic backends
29
29
  - **Marketing & launch pages** — landing pages, waitlist pages with referral mechanics, product sites with scroll animations — visual polish is a strength here
30
30
  - **API services** — backend logic exposed as REST endpoints
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindstudio-ai/remy",
3
- "version": "0.1.200",
3
+ "version": "0.1.202",
4
4
  "description": "Remy coding agent",
5
5
  "repository": {
6
6
  "type": "git",