@el4cteo/rbx-studio-mcp 0.4.6 → 0.5.2
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 +32 -1
- package/dist/bridge/api.js +22 -3
- package/dist/bridge/api.js.map +1 -1
- package/dist/bridge/console.js +348 -0
- package/dist/bridge/console.js.map +1 -0
- package/dist/bridge/harness.js +883 -0
- package/dist/bridge/harness.js.map +1 -0
- package/dist/bridge/remote.js +9 -1
- package/dist/bridge/remote.js.map +1 -1
- package/dist/bridge/rpc.js +69 -4
- package/dist/bridge/rpc.js.map +1 -1
- package/dist/bridge/server.js +54 -6
- package/dist/bridge/server.js.map +1 -1
- package/dist/doctor.js +13 -1
- package/dist/doctor.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/protocol.js.map +1 -1
- package/dist/tools/screenshot.js +7 -6
- package/dist/tools/screenshot.js.map +1 -1
- package/dist/tools/terrain.js +116 -0
- package/dist/tools/terrain.js.map +1 -0
- package/package.json +4 -4
- package/plugin/src/Commands.luau +524 -0
- package/plugin/src/Config.luau +1 -1
- package/plugin/src/Console.luau +282 -49
- package/plugin/src/Format.luau +114 -0
- package/plugin/src/Prompt.luau +436 -0
- package/plugin/src/ScriptEdit.luau +48 -0
- package/plugin/src/Visuals.luau +1409 -1332
- package/plugin/src/handlers/Capture.luau +68 -20
- package/plugin/src/handlers/Terrain.luau +371 -0
- package/plugin/src/init.server.luau +829 -725
- package/scripts/test-console.mjs +529 -0
- package/scripts/test-failover.mjs +47 -0
- package/scripts/test-plugin.mjs +85 -82
package/README.md
CHANGED
|
@@ -167,8 +167,39 @@ Gotchas: during a playtest two sessions connect — pass `studioId` explicitly a
|
|
|
167
167
|
|
|
168
168
|
Every call is logged with its latency, above a trace of the last forty. The badge names which agents are connected, with process id and uptime.
|
|
169
169
|
|
|
170
|
+
Under the header is a command line. Type a command, or type a sentence and it goes to a coding agent.
|
|
171
|
+
|
|
172
|
+
| | |
|
|
173
|
+
|---|---|
|
|
174
|
+
| `help` | list everything |
|
|
175
|
+
| `doctor` | check every part of the setup |
|
|
176
|
+
| `status` `version` `place` `clients` | what this session is |
|
|
177
|
+
| `studios` `use <n>` | which Studio window calls land on |
|
|
178
|
+
| `theme [name]` `visuals` `log [level]` `clear` `copy` | the panel itself |
|
|
179
|
+
| `port [n]` `reconnect` | the connection |
|
|
180
|
+
| `agent [use <id>\|new]` `stop` | which coding agent runs your prompts |
|
|
181
|
+
| anything else | sent to that agent |
|
|
182
|
+
|
|
183
|
+
Up and down walk the history, Tab completes.
|
|
184
|
+
|
|
185
|
+
**Prompts start a real agent.** The bridge runs whichever tool you have on PATH — Claude Code, Codex, opencode, DeepSeek Harness, Gemini, Cursor, Amp, Qwen Code, Factory Droid, goose, GitHub Copilot CLI, Aider, Crush — in headless mode. It connects back to this same bridge, drives the same Studio, and its work streams into the log line by line. It is a separate session from the terminal you may already have open, billed separately, and it is allowed the `rbx-studio` tools and nothing else. `stop` cancels it.
|
|
186
|
+
|
|
170
187
|
Hover the tab on the right edge for eight themes — Lattice, Observatory, Orbit, Void, Nebula, Aurora, Phosphor, Blueprint. Each redraws the activity band, not just its colours. Your pick is remembered.
|
|
171
188
|
|
|
189
|
+
## DeepSeek Harness (dsh)
|
|
190
|
+
|
|
191
|
+
dsh is "everything is a plugin", and this server becomes one through its MCP bridge. One overlay row, and the tools arrive as `mcp__rbx-studio__*` — the same names Claude Code and Codex use.
|
|
192
|
+
|
|
193
|
+
```sh
|
|
194
|
+
dsh --profile headless --patch config/dsh.cordis.yml "add a spawn point"
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
To keep it, append that row to your own patch layer — `$DSH_HOME/profiles/<name>/cordis.patch.yml` for one profile, `$DSH_HOME/cordis.patch.yml` for all of them. Don't overwrite the file; it may already hold unrelated patches.
|
|
198
|
+
|
|
199
|
+
`config/dsh.cordis.yml` in this repo is the row. The console panel also lists `dsh` as an agent, and passes the overlay itself, so a prompt typed there works before you have merged anything.
|
|
200
|
+
|
|
201
|
+
Two limits, both dsh's rather than ours: its headless profile prints the final answer instead of streaming, so the panel shows the reply at the end rather than step by step, and it has no `--resume`, so every prompt is a fresh conversation. It needs `DEEPSEEK_API_KEY`.
|
|
202
|
+
|
|
172
203
|
## Batching
|
|
173
204
|
|
|
174
205
|
Every write tool takes an array — ten script edits is one call.
|
|
@@ -197,7 +228,7 @@ Each batch is one **Ctrl+Z**, and all-or-nothing: a failed match leaves the plac
|
|
|
197
228
|
- **Property names are checked** against the running engine's API dump, so a typo comes back as `Anchorred` → `Anchored` instead of a runtime error.
|
|
198
229
|
- **~16k tokens of schema**, cursor-paged and capped, with `detail: concise | standard | full`.
|
|
199
230
|
|
|
200
|
-
Not built here:
|
|
231
|
+
Not built here: material generation.
|
|
201
232
|
|
|
202
233
|
## Security
|
|
203
234
|
|
package/dist/bridge/api.js
CHANGED
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
+
/**
|
|
3
|
+
* Whether this process is serving an agent a console panel started.
|
|
4
|
+
*
|
|
5
|
+
* Read from the environment at load, and read HERE rather than travelling with
|
|
6
|
+
* the client description, because the description does not exist yet at the
|
|
7
|
+
* moment that matters. A process registers the instant it starts and only
|
|
8
|
+
* learns its client's name at the MCP handshake, so the arrival -- the thing
|
|
9
|
+
* the panel logs and badges -- is announced first and described second. Sending
|
|
10
|
+
* the flag with the description meant it always arrived one announcement too
|
|
11
|
+
* late, and every prompt still flashed "2 MCP clients connected".
|
|
12
|
+
*
|
|
13
|
+
* The variable is set by `harness.run` on the agent CLI it spawns, and reaches
|
|
14
|
+
* here because that CLI starts this server as its own child.
|
|
15
|
+
*/
|
|
16
|
+
export const SPAWNED_BY_PANEL = process.env["RBX_STUDIO_MCP_SPAWNED"] === "1";
|
|
2
17
|
/**
|
|
3
18
|
* The bridge as seen from the process that actually holds the port.
|
|
4
19
|
*
|
|
@@ -31,8 +46,8 @@ export class LocalBridge {
|
|
|
31
46
|
// this class carries a client id at all: the process holding the port is
|
|
32
47
|
// one agent among however many are connected, and it is connected from the
|
|
33
48
|
// moment it starts, not from the moment it happens to ask for something.
|
|
34
|
-
inner.noteClient(this.clientId);
|
|
35
|
-
this.keepalive = setInterval(() => inner.noteClient(this.clientId), KEEPALIVE_MS);
|
|
49
|
+
inner.noteClient(this.clientId, { spawned: SPAWNED_BY_PANEL });
|
|
50
|
+
this.keepalive = setInterval(() => inner.noteClient(this.clientId, { spawned: SPAWNED_BY_PANEL }), KEEPALIVE_MS);
|
|
36
51
|
this.keepalive.unref();
|
|
37
52
|
}
|
|
38
53
|
/** Drops this process from the bridge's client list on shutdown. */
|
|
@@ -60,7 +75,11 @@ export class LocalBridge {
|
|
|
60
75
|
this.inner.notePlaceName(studioId, placeName, context);
|
|
61
76
|
}
|
|
62
77
|
describe(about) {
|
|
63
|
-
this.inner.noteClient(this.clientId, {
|
|
78
|
+
this.inner.noteClient(this.clientId, {
|
|
79
|
+
...about,
|
|
80
|
+
pid: process.pid,
|
|
81
|
+
spawned: SPAWNED_BY_PANEL,
|
|
82
|
+
});
|
|
64
83
|
}
|
|
65
84
|
}
|
|
66
85
|
//# sourceMappingURL=api.js.map
|
package/dist/bridge/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/bridge/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/bridge/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA8DzC;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,KAAK,GAAG,CAAC;AAQ9E;;;;;;;GAOG;AACH;;;;;;;;;;;GAWG;AACH,MAAM,YAAY,GAAG,MAAM,CAAC;AAE5B,MAAM,OAAO,WAAW;IAKO,KAAK;IAJzB,OAAO,GAAG,IAAI,CAAC;IACP,QAAQ,GAAG,UAAU,EAAE,CAAC;IACxB,SAAS,CAAiB;IAE3C,YAA6B,KAAa;qBAAb,KAAK;QAChC,2EAA2E;QAC3E,yEAAyE;QACzE,2EAA2E;QAC3E,yEAAyE;QACzE,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,gBAAgB,EAAE,CAAC,CAAC;QAC/D,IAAI,CAAC,SAAS,GAAG,WAAW,CAC1B,GAAG,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,gBAAgB,EAAE,CAAC,EACpE,YAAY,CACb,CAAC;QACF,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;IACzB,CAAC;IAED,oEAAoE;IACpE,OAAO;QACL,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC9B,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACzC,CAAC;IAED,IAAI,CACF,EAAU,EACV,MAAM,GAA4B,EAAE,EACpC,OAAO,GAA8C,EAAE;QAEvD,wEAAwE;QACxE,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAI,EAAE,EAAE,MAAM,EAAE,EAAE,GAAG,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;IACjF,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrC,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;YACvB,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC;YAC5C,cAAc,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC;SACzD,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,QAAgB;QAC9B,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAChD,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,QAAgB,EAAE,SAAiB,EAAE,OAAgB;QACvE,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,QAAQ,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IACzD,CAAC;IAED,QAAQ,CAAC,KAAwB;QAC/B,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE;YACnC,GAAG,KAAK;YACR,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,OAAO,EAAE,gBAAgB;SAC1B,CAAC,CAAC;IACL,CAAC;CACF"}
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The half of the console panel's command line that Studio cannot answer.
|
|
3
|
+
*
|
|
4
|
+
* Most of what someone types into the panel is answered inside the plugin --
|
|
5
|
+
* `clear`, `theme`, `status` are all facts Studio already holds. The rest are
|
|
6
|
+
* questions about the machine: is the port really ours, is the installed plugin
|
|
7
|
+
* the one this package ships, which Studios are attached, and what happens when
|
|
8
|
+
* a free-text prompt is typed at an agent. Those live here.
|
|
9
|
+
*
|
|
10
|
+
* Answers come back as console rows rather than as prose, so the panel prints
|
|
11
|
+
* them with the same sigils, colours and columns as everything else it logs. A
|
|
12
|
+
* diagnostic that renders differently from the log it appears in reads as a
|
|
13
|
+
* foreign object pasted into the session.
|
|
14
|
+
*/
|
|
15
|
+
import { collectChecks } from "../doctor.js";
|
|
16
|
+
import { find, installed, matchesClient, run, } from "./harness.js";
|
|
17
|
+
const agents = new Map();
|
|
18
|
+
function agentFor(studioId) {
|
|
19
|
+
let state = agents.get(studioId);
|
|
20
|
+
if (state === undefined) {
|
|
21
|
+
state = { harnessId: null, session: null, active: null };
|
|
22
|
+
agents.set(studioId, state);
|
|
23
|
+
}
|
|
24
|
+
return state;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Whether an agent is mid-run for this Studio.
|
|
28
|
+
*
|
|
29
|
+
* The panel dims its caret while one is, and the only other signal it gets is
|
|
30
|
+
* the `agent-run` event fired when the run ends. A prompt that never started --
|
|
31
|
+
* no harness installed, or a name that resolves to nothing -- fires no such
|
|
32
|
+
* event, so the panel would sit "busy" forever on the one failure it is most
|
|
33
|
+
* likely to hit first. Answered with the reply instead.
|
|
34
|
+
*/
|
|
35
|
+
export function agentRunning(studioId) {
|
|
36
|
+
return agents.get(studioId)?.active !== null && agents.get(studioId)?.active !== undefined;
|
|
37
|
+
}
|
|
38
|
+
const MARK = {
|
|
39
|
+
ok: "ok",
|
|
40
|
+
warn: "warn",
|
|
41
|
+
bad: "error",
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* `doctor`, rendered for a log rather than for a terminal.
|
|
45
|
+
*
|
|
46
|
+
* The CLI prints a blank line between checks and indents the explanation under
|
|
47
|
+
* the title. Neither survives here: the console already separates rows and
|
|
48
|
+
* already indents continuations, so the same output pasted in would be double
|
|
49
|
+
* spaced and double indented. The checks are shared; only the shaping differs.
|
|
50
|
+
*/
|
|
51
|
+
function renderChecks(checks) {
|
|
52
|
+
const lines = [];
|
|
53
|
+
for (const check of checks) {
|
|
54
|
+
lines.push({
|
|
55
|
+
level: MARK[check.status],
|
|
56
|
+
message: check.title,
|
|
57
|
+
// The CLI's own two-space indent, undone: it exists to hang the detail
|
|
58
|
+
// under the title in a flat stream of text, and this log does that itself.
|
|
59
|
+
detail: check.detail.split("\n").map((piece) => piece.trim()).join(" "),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
const bad = checks.filter((check) => check.status === "bad").length;
|
|
63
|
+
const warn = checks.filter((check) => check.status === "warn").length;
|
|
64
|
+
lines.push({
|
|
65
|
+
level: bad > 0 ? "error" : warn > 0 ? "warn" : "ok",
|
|
66
|
+
message: `${checks.length - bad - warn} passed, ${warn} warning, ${bad} failure`,
|
|
67
|
+
});
|
|
68
|
+
return lines;
|
|
69
|
+
}
|
|
70
|
+
/** The Studios on this bridge, with the one calls land on marked. */
|
|
71
|
+
function renderStudios(bridge, asking) {
|
|
72
|
+
const sessions = bridge.list();
|
|
73
|
+
if (sessions.length === 0)
|
|
74
|
+
return [{ level: "dim", message: "no Studio is connected" }];
|
|
75
|
+
const active = bridge.activeId("panel");
|
|
76
|
+
return [
|
|
77
|
+
{
|
|
78
|
+
level: "info",
|
|
79
|
+
message: `${sessions.length} Studio${sessions.length === 1 ? "" : "s"} on this bridge`,
|
|
80
|
+
},
|
|
81
|
+
...sessions.map((session, index) => {
|
|
82
|
+
const here = session.studioId === asking ? " (this panel)" : "";
|
|
83
|
+
return {
|
|
84
|
+
level: session.studioId === active ? "ok" : "dim",
|
|
85
|
+
message: ` ${index + 1}. ${session.placeName}${here}`,
|
|
86
|
+
detail: `${session.context ?? "edit"} ${session.transport} ${session.studioId.slice(0, 8)}`,
|
|
87
|
+
};
|
|
88
|
+
}),
|
|
89
|
+
];
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Resolves what someone typed after `use` into a studioId.
|
|
93
|
+
*
|
|
94
|
+
* Both forms are accepted because both are in front of them: `studios` prints a
|
|
95
|
+
* number, and the tools print an id. Requiring the id would mean reading a
|
|
96
|
+
* hex string off one line to type it into the next.
|
|
97
|
+
*/
|
|
98
|
+
function resolveStudio(bridge, token) {
|
|
99
|
+
const sessions = bridge.list();
|
|
100
|
+
const index = Number(token);
|
|
101
|
+
if (Number.isInteger(index) && index >= 1 && index <= sessions.length) {
|
|
102
|
+
return sessions[index - 1]?.studioId ?? null;
|
|
103
|
+
}
|
|
104
|
+
const byId = sessions.find((session) => session.studioId.startsWith(token));
|
|
105
|
+
return byId?.studioId ?? null;
|
|
106
|
+
}
|
|
107
|
+
/** Which harnesses are installed, and which one prompts will go to. */
|
|
108
|
+
function renderAgents(state, bridge) {
|
|
109
|
+
const available = installed();
|
|
110
|
+
if (available.length === 0) {
|
|
111
|
+
return [
|
|
112
|
+
{
|
|
113
|
+
level: "warn",
|
|
114
|
+
message: "no coding agent found on PATH",
|
|
115
|
+
detail: "Install one (claude, codex, opencode, gemini, cursor-agent) and it " +
|
|
116
|
+
"will be picked up automatically. Commands still work without one.",
|
|
117
|
+
},
|
|
118
|
+
];
|
|
119
|
+
}
|
|
120
|
+
//[[ Says what a prompt WOULD do, not what the registry contains.
|
|
121
|
+
//
|
|
122
|
+
// Listing an "(in use)" that the prompt then ignores is worse than not
|
|
123
|
+
// marking one at all -- that mismatch is what made the wrong agent answer
|
|
124
|
+
// look like a bug in the agent rather than in this choice. So the mark comes
|
|
125
|
+
// from the same function the prompt uses, and when that cannot decide,
|
|
126
|
+
// nothing is marked and the list says so.
|
|
127
|
+
//]]
|
|
128
|
+
const wanted = preferred(state, bridge, available);
|
|
129
|
+
const connected = (entry) => bridge.clientList().some((client) => matchesClient(entry, client.name));
|
|
130
|
+
return [
|
|
131
|
+
{ level: "info", message: `${available.length} agent${available.length === 1 ? "" : "s"} available` },
|
|
132
|
+
...available.map((entry) => {
|
|
133
|
+
const marks = [];
|
|
134
|
+
if (connected(entry))
|
|
135
|
+
marks.push("connected");
|
|
136
|
+
if (entry.id === wanted?.id)
|
|
137
|
+
marks.push(state.harnessId === null ? "would answer" : "in use");
|
|
138
|
+
return {
|
|
139
|
+
level: entry.id === wanted?.id ? "ok" : "dim",
|
|
140
|
+
message: ` ${entry.id}`,
|
|
141
|
+
detail: marks.length > 0 ? `${entry.label} (${marks.join(", ")})` : entry.label,
|
|
142
|
+
};
|
|
143
|
+
}),
|
|
144
|
+
wanted === undefined
|
|
145
|
+
? { level: "warn", message: "several agents are connected -- `agent use <id>` to pick one" }
|
|
146
|
+
: { level: "dim", message: "type anything that is not a command to send it to the agent" },
|
|
147
|
+
];
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Which agent an unconfigured panel should use, or nothing when it cannot tell.
|
|
151
|
+
*
|
|
152
|
+
* The old rule was "the first one installed", which is alphabetical accident
|
|
153
|
+
* dressed up as a decision: someone with opencode open typed a prompt and
|
|
154
|
+
* Claude Code answered, because `claude` sits first in the registry. The answer
|
|
155
|
+
* was fine and came from the wrong program.
|
|
156
|
+
*
|
|
157
|
+
* The bridge knows more than the registry does. Every agent driving this Studio
|
|
158
|
+
* is connected to it and says its name, so the panel can answer with the one
|
|
159
|
+
* the user is actually working in. That settles the ordinary case, which is one
|
|
160
|
+
* agent attached to one Studio.
|
|
161
|
+
*
|
|
162
|
+
* When two are attached there is no signal that says which one the user meant.
|
|
163
|
+
* Recency does not: in the report that prompted this, opencode had been up a
|
|
164
|
+
* minute and Claude Code forty seconds, and the wanted answer was the older
|
|
165
|
+
* one. So this returns nothing and the caller asks -- the same shape as
|
|
166
|
+
* AMBIGUOUS_STUDIO, which refuses to guess between two open places for exactly
|
|
167
|
+
* this reason. The choice is remembered, so it is one question once.
|
|
168
|
+
*/
|
|
169
|
+
function preferred(state, bridge, available) {
|
|
170
|
+
if (state.harnessId !== null)
|
|
171
|
+
return find(state.harnessId);
|
|
172
|
+
if (available.length === 1)
|
|
173
|
+
return available[0];
|
|
174
|
+
const matched = available.filter((harness) => bridge.clientList().some((client) => matchesClient(harness, client.name)));
|
|
175
|
+
if (matched.length === 1)
|
|
176
|
+
return matched[0];
|
|
177
|
+
// Nothing connected that we recognise: the registry order is as good a guess
|
|
178
|
+
// as any, and refusing here would block a panel whose user has no agent
|
|
179
|
+
// attached at all -- which is a normal way to use this.
|
|
180
|
+
return matched.length === 0 ? available[0] : undefined;
|
|
181
|
+
}
|
|
182
|
+
/** Asks which of several attached agents should answer, and how to say so. */
|
|
183
|
+
function askWhichAgent(bridge, available) {
|
|
184
|
+
const attached = available.filter((harness) => bridge.clientList().some((client) => matchesClient(harness, client.name)));
|
|
185
|
+
return [
|
|
186
|
+
{
|
|
187
|
+
level: "warn",
|
|
188
|
+
message: `${attached.length} agents are connected to this Studio -- which should answer?`,
|
|
189
|
+
},
|
|
190
|
+
...attached.map((harness) => ({
|
|
191
|
+
level: "dim",
|
|
192
|
+
message: ` agent use ${harness.id}`,
|
|
193
|
+
detail: harness.label,
|
|
194
|
+
})),
|
|
195
|
+
{ level: "dim", message: "asked once; the choice is remembered until you change it" },
|
|
196
|
+
];
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Says where the prompt came from, once at the start of a conversation.
|
|
200
|
+
*
|
|
201
|
+
* The agent is spawned in this repository's working directory, so its instinct
|
|
202
|
+
* for "create a simple script, then edit it" is a file on disk -- which is what
|
|
203
|
+
* happened the first time this was tried: it reached for Bash and `cat >`, was
|
|
204
|
+
* refused by its own permission mode, and reported the test impossible. The
|
|
205
|
+
* user was sitting in front of a Roblox place at the time.
|
|
206
|
+
*
|
|
207
|
+
* Nothing about the prompt itself says otherwise. It arrives as a bare line
|
|
208
|
+
* typed into a panel, with none of the context the panel has: that there is a
|
|
209
|
+
* place open, that the tools reaching it are already connected, and that
|
|
210
|
+
* "script" almost certainly means a Script instance rather than a source file.
|
|
211
|
+
*
|
|
212
|
+
* Only on the first turn. A continued session already has this in its history,
|
|
213
|
+
* and repeating it every message would spend context restating something the
|
|
214
|
+
* agent has not forgotten -- and would start reading as nagging.
|
|
215
|
+
*/
|
|
216
|
+
export function frame(prompt) {
|
|
217
|
+
return ("You are answering someone typing into a console panel docked inside " +
|
|
218
|
+
"Roblox Studio, with a place open in front of them. The rbx-studio MCP " +
|
|
219
|
+
"tools are connected to that place: use them for anything about the " +
|
|
220
|
+
"game -- `script_create` and `script_edit` for code, `create`/`modify` " +
|
|
221
|
+
"for instances, `terrain` for terrain. \"A script\" means a Script " +
|
|
222
|
+
"instance in the place, not a file on disk. Reach for the filesystem " +
|
|
223
|
+
"only if they clearly mean this MCP server's own source.\n\n" +
|
|
224
|
+
prompt);
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Starts an agent on `prompt` and streams its work into the asking panel.
|
|
228
|
+
*
|
|
229
|
+
* Returns immediately with the rows that describe the start. Everything the
|
|
230
|
+
* agent then does arrives over the Studio's own event stream, which is why this
|
|
231
|
+
* needs the bridge and not just a return value: a run takes minutes, and a
|
|
232
|
+
* request held open that long is a request that times out.
|
|
233
|
+
*/
|
|
234
|
+
function startAgent(bridge, state, studioId, prompt) {
|
|
235
|
+
if (state.active !== null) {
|
|
236
|
+
return [{ level: "warn", message: "an agent is already running", detail: "type stop to cancel it" }];
|
|
237
|
+
}
|
|
238
|
+
const available = installed();
|
|
239
|
+
if (available.length === 0)
|
|
240
|
+
return renderAgents(state, bridge);
|
|
241
|
+
const wanted = preferred(state, bridge, available);
|
|
242
|
+
if (wanted === undefined) {
|
|
243
|
+
return state.harnessId === null
|
|
244
|
+
? askWhichAgent(bridge, available)
|
|
245
|
+
: [{ level: "error", message: `unknown agent "${state.harnessId}"` }];
|
|
246
|
+
}
|
|
247
|
+
const emit = (line) => {
|
|
248
|
+
bridge.notify(studioId, { event: "console", ...line });
|
|
249
|
+
};
|
|
250
|
+
const started = run(wanted, state.session === null ? frame(prompt) : prompt, {
|
|
251
|
+
cwd: process.cwd(),
|
|
252
|
+
session: state.session,
|
|
253
|
+
emit,
|
|
254
|
+
});
|
|
255
|
+
state.active = started;
|
|
256
|
+
void started.done.then((session) => {
|
|
257
|
+
state.session = session;
|
|
258
|
+
state.active = null;
|
|
259
|
+
bridge.notify(studioId, { event: "agent-run", state: "idle" });
|
|
260
|
+
});
|
|
261
|
+
//[[ The panel already echoed the line; this says who picked it up.
|
|
262
|
+
//
|
|
263
|
+
// It used to repeat the prompt, which put the same sentence on screen twice
|
|
264
|
+
// in the same second under two different labels -- the second one reading as
|
|
265
|
+
// if the agent had said it back.
|
|
266
|
+
//]]
|
|
267
|
+
return [
|
|
268
|
+
{
|
|
269
|
+
level: "dim",
|
|
270
|
+
message: wanted.label,
|
|
271
|
+
detail: state.session === null ? "starting" : "continuing",
|
|
272
|
+
},
|
|
273
|
+
];
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Answers one line typed into a console panel.
|
|
277
|
+
*
|
|
278
|
+
* Unknown commands are not an error here: the plugin decides what is a command
|
|
279
|
+
* and what is a prompt before it ever reaches this file, so anything arriving
|
|
280
|
+
* with a name we do not know is a genuine mismatch between plugin and server
|
|
281
|
+
* versions, and saying so beats guessing.
|
|
282
|
+
*/
|
|
283
|
+
export async function handleConsole(bridge, port, request) {
|
|
284
|
+
const state = agentFor(request.studioId);
|
|
285
|
+
switch (request.command) {
|
|
286
|
+
case "doctor":
|
|
287
|
+
return renderChecks(await collectChecks(port));
|
|
288
|
+
case "studios":
|
|
289
|
+
return renderStudios(bridge, request.studioId);
|
|
290
|
+
case "use": {
|
|
291
|
+
const token = request.args[0];
|
|
292
|
+
if (token === undefined) {
|
|
293
|
+
return [{ level: "dim", message: "usage: use <number|id>", detail: "run studios to see them" }];
|
|
294
|
+
}
|
|
295
|
+
const target = resolveStudio(bridge, token);
|
|
296
|
+
if (target === null)
|
|
297
|
+
return [{ level: "error", message: `no Studio matches "${token}"` }];
|
|
298
|
+
bridge.setActiveForAll(target);
|
|
299
|
+
const name = bridge.list().find((session) => session.studioId === target)?.placeName ?? target;
|
|
300
|
+
return [{ level: "ok", message: `calls now target ${name}` }];
|
|
301
|
+
}
|
|
302
|
+
case "agent": {
|
|
303
|
+
const verb = request.args[0];
|
|
304
|
+
if (verb === undefined || verb === "list")
|
|
305
|
+
return renderAgents(state, bridge);
|
|
306
|
+
if (verb === "use") {
|
|
307
|
+
const id = request.args[1];
|
|
308
|
+
if (id === undefined)
|
|
309
|
+
return [{ level: "dim", message: "usage: agent use <id>" }];
|
|
310
|
+
const wanted = find(id);
|
|
311
|
+
if (wanted === undefined)
|
|
312
|
+
return [{ level: "error", message: `unknown agent "${id}"` }];
|
|
313
|
+
state.harnessId = wanted.id;
|
|
314
|
+
// A new harness cannot continue another harness's conversation, and
|
|
315
|
+
// handing it a stranger's session id fails in a way that reads as the
|
|
316
|
+
// switch itself being broken.
|
|
317
|
+
state.session = null;
|
|
318
|
+
return [{ level: "ok", message: `prompts now go to ${wanted.label}` }];
|
|
319
|
+
}
|
|
320
|
+
if (verb === "new") {
|
|
321
|
+
state.session = null;
|
|
322
|
+
return [{ level: "ok", message: "next prompt starts a fresh conversation" }];
|
|
323
|
+
}
|
|
324
|
+
return [{ level: "dim", message: "usage: agent [list|use <id>|new]" }];
|
|
325
|
+
}
|
|
326
|
+
case "stop": {
|
|
327
|
+
if (state.active === null)
|
|
328
|
+
return [{ level: "dim", message: "nothing is running" }];
|
|
329
|
+
state.active.cancel();
|
|
330
|
+
return [{ level: "warn", message: "stopping the agent" }];
|
|
331
|
+
}
|
|
332
|
+
case "prompt": {
|
|
333
|
+
const prompt = request.line.trim();
|
|
334
|
+
if (prompt === "")
|
|
335
|
+
return [];
|
|
336
|
+
return startAgent(bridge, state, request.studioId, prompt);
|
|
337
|
+
}
|
|
338
|
+
default:
|
|
339
|
+
return [
|
|
340
|
+
{
|
|
341
|
+
level: "error",
|
|
342
|
+
message: `this bridge does not know "${request.command}"`,
|
|
343
|
+
detail: "the plugin and the server are probably different builds",
|
|
344
|
+
},
|
|
345
|
+
];
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
//# sourceMappingURL=console.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"console.js","sourceRoot":"","sources":["../../src/bridge/console.ts"],"names":[],"mappings":"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"}
|