@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 +12 -1
- package/dist/headless.d.ts +9 -7
- package/dist/headless.js +91 -40
- package/dist/index.js +91 -40
- package/dist/prompt/compiled/auth.md +1 -1
- package/dist/prompt/compiled/dev-and-deploy.md +1 -1
- package/dist/prompt/compiled/interfaces.md +142 -53
- package/dist/prompt/compiled/manifest.md +1 -1
- package/dist/prompt/compiled/mcp-interfaces.md +34 -0
- package/dist/prompt/compiled/methods.md +1 -1
- package/dist/prompt/compiled/platform.md +6 -4
- package/dist/prompt/static/intake.md +2 -2
- package/package.json +1 -1
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
|
|
package/dist/headless.d.ts
CHANGED
|
@@ -68,14 +68,9 @@ declare class HeadlessSession {
|
|
|
68
68
|
start(): Promise<void>;
|
|
69
69
|
private shutdown;
|
|
70
70
|
private emit;
|
|
71
|
-
/**
|
|
72
|
-
*
|
|
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
|
-
-
|
|
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(), () =>
|
|
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"
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
7820
|
-
|
|
7821
|
-
|
|
7822
|
-
|
|
7823
|
-
|
|
7824
|
-
|
|
7825
|
-
|
|
7826
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
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(), () =>
|
|
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"
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
8620
|
-
|
|
8621
|
-
|
|
8622
|
-
|
|
8623
|
-
|
|
8624
|
-
|
|
8625
|
-
|
|
8626
|
-
|
|
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
|
-
|
|
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
|
|
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/
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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
|
-
"
|
|
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
|
-
|
|
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`, `
|
|
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,
|
|
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/
|
|
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,
|
|
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,
|
|
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
|
-
- **
|
|
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
|