symposium-mcp 0.2.0 → 0.3.0
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 +26 -7
- package/dist/mcp.js +574 -82
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,21 @@ UI on a shared stage. It lets an agent join a space, read the transcript, post m
|
|
|
5
5
|
and put surfaces on the stage. It runs on your machine and talks to the Symposium site
|
|
6
6
|
you connect it to.
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## Set up
|
|
9
|
+
|
|
10
|
+
In Symposium, press **Connect your agent** and run the line it shows in a terminal:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npx -y symposium-mcp setup symc_…
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
It finds the agents on the computer (Claude Code, Codex, Cursor, Gemini CLI, Windsurf),
|
|
17
|
+
gives each this server, and connects each to your account with the one code. It then
|
|
18
|
+
offers to open Claude Code or Codex already in the meeting. Later, start a session in any
|
|
19
|
+
of them and say "join my meeting". Running it again is safe, and without a code it only
|
|
20
|
+
adds the server.
|
|
21
|
+
|
|
22
|
+
## By hand
|
|
9
23
|
|
|
10
24
|
Give it to each agent once.
|
|
11
25
|
|
|
@@ -22,15 +36,20 @@ Any other MCP client: add it to the client's MCP configuration.
|
|
|
22
36
|
{ "mcpServers": { "symposium": { "command": "npx", "args": ["-y", "symposium-mcp"] } } }
|
|
23
37
|
```
|
|
24
38
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
(`Connect to my Symposium: symc_…`). After that, "join my meeting" is enough.
|
|
39
|
+
Then, in Symposium, press **Connect your agent** and paste the line under **Or connect
|
|
40
|
+
one agent by hand** to the agent (`Connect to my Symposium: symc_…`). After that, "join
|
|
41
|
+
my meeting" is enough.
|
|
29
42
|
|
|
30
43
|
Each MCP client is its own agent: the server names itself after the client that runs it
|
|
31
44
|
("Ada's Claude", "Ada's Codex") and keeps each one's connection in a file of its own
|
|
32
|
-
under `~/.symposium/`, readable only by you.
|
|
33
|
-
connect code
|
|
45
|
+
under `~/.symposium/`, readable only by you. Connecting by hand, each agent needs a
|
|
46
|
+
connect code of its own.
|
|
47
|
+
|
|
48
|
+
People in a space can reach the agent only while it is waiting on the space
|
|
49
|
+
(`wait_for_events`); the room shows it as listening or not. For the time in between,
|
|
50
|
+
`symposium-mcp wake --space=<name or id>` waits in the background, prints the first
|
|
51
|
+
request addressed to the agent as a line of JSON and exits, which wakes an agent whose
|
|
52
|
+
client reports finished background commands. `join_space` returns the exact command.
|
|
34
53
|
|
|
35
54
|
| Variable | What it does |
|
|
36
55
|
| ---------------------- | -------------------------------------------------------------------- |
|
package/dist/mcp.js
CHANGED
|
@@ -3231,8 +3231,8 @@ var require_utils = __commonJS({
|
|
|
3231
3231
|
}
|
|
3232
3232
|
return ind;
|
|
3233
3233
|
}
|
|
3234
|
-
function removeDotSegments(
|
|
3235
|
-
let input2 =
|
|
3234
|
+
function removeDotSegments(path4) {
|
|
3235
|
+
let input2 = path4;
|
|
3236
3236
|
const output2 = [];
|
|
3237
3237
|
let nextSlash = -1;
|
|
3238
3238
|
let len = 0;
|
|
@@ -3484,8 +3484,8 @@ var require_schemes = __commonJS({
|
|
|
3484
3484
|
wsComponent.secure = void 0;
|
|
3485
3485
|
}
|
|
3486
3486
|
if (wsComponent.resourceName) {
|
|
3487
|
-
const [
|
|
3488
|
-
wsComponent.path =
|
|
3487
|
+
const [path4, query] = wsComponent.resourceName.split("?");
|
|
3488
|
+
wsComponent.path = path4 && path4 !== "/" ? path4 : void 0;
|
|
3489
3489
|
wsComponent.query = query;
|
|
3490
3490
|
wsComponent.resourceName = void 0;
|
|
3491
3491
|
}
|
|
@@ -6898,7 +6898,7 @@ var require_dist = __commonJS({
|
|
|
6898
6898
|
});
|
|
6899
6899
|
|
|
6900
6900
|
// src/mcp.ts
|
|
6901
|
-
import { hostname as
|
|
6901
|
+
import { hostname as hostname4 } from "node:os";
|
|
6902
6902
|
|
|
6903
6903
|
// ../node_modules/.pnpm/zod@4.6.7/node_modules/zod/v3/helpers/util.js
|
|
6904
6904
|
var util;
|
|
@@ -7274,8 +7274,8 @@ function getErrorMap() {
|
|
|
7274
7274
|
|
|
7275
7275
|
// ../node_modules/.pnpm/zod@4.6.7/node_modules/zod/v3/helpers/parseUtil.js
|
|
7276
7276
|
var makeIssue = (params) => {
|
|
7277
|
-
const { data, path:
|
|
7278
|
-
const fullPath = [...
|
|
7277
|
+
const { data, path: path4, errorMaps, issueData } = params;
|
|
7278
|
+
const fullPath = [...path4, ...issueData.path || []];
|
|
7279
7279
|
const fullIssue = {
|
|
7280
7280
|
...issueData,
|
|
7281
7281
|
path: fullPath
|
|
@@ -7390,11 +7390,11 @@ var errorUtil;
|
|
|
7390
7390
|
|
|
7391
7391
|
// ../node_modules/.pnpm/zod@4.6.7/node_modules/zod/v3/types.js
|
|
7392
7392
|
var ParseInputLazyPath = class {
|
|
7393
|
-
constructor(parent, value,
|
|
7393
|
+
constructor(parent, value, path4, key) {
|
|
7394
7394
|
this._cachedPath = [];
|
|
7395
7395
|
this.parent = parent;
|
|
7396
7396
|
this.data = value;
|
|
7397
|
-
this._path =
|
|
7397
|
+
this._path = path4;
|
|
7398
7398
|
this._key = key;
|
|
7399
7399
|
}
|
|
7400
7400
|
get path() {
|
|
@@ -11349,10 +11349,10 @@ function mergeDefs(...defs) {
|
|
|
11349
11349
|
function cloneDef(schema) {
|
|
11350
11350
|
return mergeDefs(schema._zod.def);
|
|
11351
11351
|
}
|
|
11352
|
-
function getElementAtPath(obj,
|
|
11353
|
-
if (!
|
|
11352
|
+
function getElementAtPath(obj, path4) {
|
|
11353
|
+
if (!path4)
|
|
11354
11354
|
return obj;
|
|
11355
|
-
return
|
|
11355
|
+
return path4.reduce((acc, key) => acc?.[key], obj);
|
|
11356
11356
|
}
|
|
11357
11357
|
function promiseAllObject(promisesObj) {
|
|
11358
11358
|
const keys = Object.keys(promisesObj);
|
|
@@ -11692,11 +11692,11 @@ function explicitlyAborted(x, startIndex = 0) {
|
|
|
11692
11692
|
}
|
|
11693
11693
|
return false;
|
|
11694
11694
|
}
|
|
11695
|
-
function prefixIssues(
|
|
11695
|
+
function prefixIssues(path4, issues) {
|
|
11696
11696
|
return issues.map((iss) => {
|
|
11697
11697
|
var _a3;
|
|
11698
11698
|
(_a3 = iss).path ?? (_a3.path = []);
|
|
11699
|
-
iss.path.unshift(
|
|
11699
|
+
iss.path.unshift(path4);
|
|
11700
11700
|
return iss;
|
|
11701
11701
|
});
|
|
11702
11702
|
}
|
|
@@ -12158,16 +12158,16 @@ function flattenError(error62, mapper = (issue2) => issue2.message) {
|
|
|
12158
12158
|
}
|
|
12159
12159
|
function formatError(error62, mapper = (issue2) => issue2.message) {
|
|
12160
12160
|
const fieldErrors = { _errors: [] };
|
|
12161
|
-
const processError = (error63,
|
|
12161
|
+
const processError = (error63, path4 = []) => {
|
|
12162
12162
|
for (const issue2 of error63.issues) {
|
|
12163
12163
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
12164
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
12164
|
+
issue2.errors.map((issues) => processError({ issues }, [...path4, ...issue2.path]));
|
|
12165
12165
|
} else if (issue2.code === "invalid_key") {
|
|
12166
|
-
processError({ issues: issue2.issues }, [...
|
|
12166
|
+
processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
|
|
12167
12167
|
} else if (issue2.code === "invalid_element") {
|
|
12168
|
-
processError({ issues: issue2.issues }, [...
|
|
12168
|
+
processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
|
|
12169
12169
|
} else {
|
|
12170
|
-
const fullpath = [...
|
|
12170
|
+
const fullpath = [...path4, ...issue2.path];
|
|
12171
12171
|
if (fullpath.length === 0) {
|
|
12172
12172
|
fieldErrors._errors.push(mapper(issue2));
|
|
12173
12173
|
} else {
|
|
@@ -12206,17 +12206,17 @@ function formatError(error62, mapper = (issue2) => issue2.message) {
|
|
|
12206
12206
|
}
|
|
12207
12207
|
function treeifyError(error62, mapper = (issue2) => issue2.message) {
|
|
12208
12208
|
const result = { errors: [] };
|
|
12209
|
-
const processError = (error63,
|
|
12209
|
+
const processError = (error63, path4 = []) => {
|
|
12210
12210
|
var _a3;
|
|
12211
12211
|
for (const issue2 of error63.issues) {
|
|
12212
12212
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
12213
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
12213
|
+
issue2.errors.map((issues) => processError({ issues }, [...path4, ...issue2.path]));
|
|
12214
12214
|
} else if (issue2.code === "invalid_key") {
|
|
12215
|
-
processError({ issues: issue2.issues }, [...
|
|
12215
|
+
processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
|
|
12216
12216
|
} else if (issue2.code === "invalid_element") {
|
|
12217
|
-
processError({ issues: issue2.issues }, [...
|
|
12217
|
+
processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
|
|
12218
12218
|
} else {
|
|
12219
|
-
const fullpath = [...
|
|
12219
|
+
const fullpath = [...path4, ...issue2.path];
|
|
12220
12220
|
if (fullpath.length === 0) {
|
|
12221
12221
|
result.errors.push(mapper(issue2));
|
|
12222
12222
|
continue;
|
|
@@ -12255,8 +12255,8 @@ function treeifyError(error62, mapper = (issue2) => issue2.message) {
|
|
|
12255
12255
|
}
|
|
12256
12256
|
function toDotPath(_path) {
|
|
12257
12257
|
const segs = [];
|
|
12258
|
-
const
|
|
12259
|
-
for (const seg of
|
|
12258
|
+
const path4 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
12259
|
+
for (const seg of path4) {
|
|
12260
12260
|
if (typeof seg === "number")
|
|
12261
12261
|
segs.push(`[${seg}]`);
|
|
12262
12262
|
else if (typeof seg === "symbol")
|
|
@@ -13358,9 +13358,9 @@ var asciiTabOrNewline = /[\t\n\r]/g;
|
|
|
13358
13358
|
function stripTabAndNewline(value) {
|
|
13359
13359
|
return value.replace(asciiTabOrNewline, "");
|
|
13360
13360
|
}
|
|
13361
|
-
function urlHostnameOk(url2,
|
|
13362
|
-
|
|
13363
|
-
return
|
|
13361
|
+
function urlHostnameOk(url2, hostname5) {
|
|
13362
|
+
hostname5.lastIndex = 0;
|
|
13363
|
+
return hostname5.test(url2.hostname);
|
|
13364
13364
|
}
|
|
13365
13365
|
function urlProtocolOk(url2, protocol) {
|
|
13366
13366
|
protocol.lastIndex = 0;
|
|
@@ -27807,11 +27807,11 @@ function normalizeObjectSchema(schema) {
|
|
|
27807
27807
|
}
|
|
27808
27808
|
return void 0;
|
|
27809
27809
|
}
|
|
27810
|
-
function getDotPath(
|
|
27811
|
-
if (
|
|
27810
|
+
function getDotPath(path4) {
|
|
27811
|
+
if (path4.length === 0) {
|
|
27812
27812
|
return "object root";
|
|
27813
27813
|
}
|
|
27814
|
-
return
|
|
27814
|
+
return path4.reduce((acc, seg, index) => {
|
|
27815
27815
|
if (index === 0) {
|
|
27816
27816
|
return String(seg);
|
|
27817
27817
|
}
|
|
@@ -30038,13 +30038,13 @@ function resolveRef(ref, ctx) {
|
|
|
30038
30038
|
if (!ref.startsWith("#")) {
|
|
30039
30039
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
30040
30040
|
}
|
|
30041
|
-
const
|
|
30042
|
-
if (
|
|
30041
|
+
const path4 = ref.slice(1).split("/").filter(Boolean);
|
|
30042
|
+
if (path4.length === 0) {
|
|
30043
30043
|
return ctx.rootSchema;
|
|
30044
30044
|
}
|
|
30045
30045
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
30046
|
-
if (
|
|
30047
|
-
const key =
|
|
30046
|
+
if (path4[0] === defsKey) {
|
|
30047
|
+
const key = path4[1] === void 0 ? void 0 : decodeJSONPointerSegment(path4[1]);
|
|
30048
30048
|
if (!key || !ctx.defs[key]) {
|
|
30049
30049
|
throw new Error(`Reference not found: ${ref}`);
|
|
30050
30050
|
}
|
|
@@ -33507,13 +33507,13 @@ var zodToJsonSchema = (schema, options) => {
|
|
|
33507
33507
|
}, true) ?? parseAnyDef(refs)
|
|
33508
33508
|
}), {}) : void 0;
|
|
33509
33509
|
const name = typeof options === "string" ? options : options?.nameStrategy === "title" ? void 0 : options?.name;
|
|
33510
|
-
const
|
|
33510
|
+
const main2 = parseDef(schema._def, name === void 0 ? refs : {
|
|
33511
33511
|
...refs,
|
|
33512
33512
|
currentPath: [...refs.basePath, refs.definitionPath, name]
|
|
33513
33513
|
}, false) ?? parseAnyDef(refs);
|
|
33514
33514
|
const title = typeof options === "object" && options.name !== void 0 && options.nameStrategy === "title" ? options.name : void 0;
|
|
33515
33515
|
if (title !== void 0) {
|
|
33516
|
-
|
|
33516
|
+
main2.title = title;
|
|
33517
33517
|
}
|
|
33518
33518
|
if (refs.flags.hasReferencedOpenAiAnyType) {
|
|
33519
33519
|
if (!definitions) {
|
|
@@ -33534,9 +33534,9 @@ var zodToJsonSchema = (schema, options) => {
|
|
|
33534
33534
|
}
|
|
33535
33535
|
}
|
|
33536
33536
|
const combined = name === void 0 ? definitions ? {
|
|
33537
|
-
...
|
|
33537
|
+
...main2,
|
|
33538
33538
|
[refs.definitionPath]: definitions
|
|
33539
|
-
} :
|
|
33539
|
+
} : main2 : {
|
|
33540
33540
|
$ref: [
|
|
33541
33541
|
...refs.$refStrategy === "relative" ? [] : refs.basePath,
|
|
33542
33542
|
refs.definitionPath,
|
|
@@ -33544,7 +33544,7 @@ var zodToJsonSchema = (schema, options) => {
|
|
|
33544
33544
|
].join("/"),
|
|
33545
33545
|
[refs.definitionPath]: {
|
|
33546
33546
|
...definitions,
|
|
33547
|
-
[name]:
|
|
33547
|
+
[name]: main2
|
|
33548
33548
|
}
|
|
33549
33549
|
};
|
|
33550
33550
|
if (refs.target === "jsonSchema7") {
|
|
@@ -34190,7 +34190,7 @@ var Protocol = class {
|
|
|
34190
34190
|
}
|
|
34191
34191
|
};
|
|
34192
34192
|
}
|
|
34193
|
-
const cancel = (
|
|
34193
|
+
const cancel = (reason2) => {
|
|
34194
34194
|
this._responseHandlers.delete(messageId);
|
|
34195
34195
|
this._progressHandlers.delete(messageId);
|
|
34196
34196
|
this._cleanupTimeout(messageId);
|
|
@@ -34199,10 +34199,10 @@ var Protocol = class {
|
|
|
34199
34199
|
method: "notifications/cancelled",
|
|
34200
34200
|
params: {
|
|
34201
34201
|
requestId: messageId,
|
|
34202
|
-
reason: String(
|
|
34202
|
+
reason: String(reason2)
|
|
34203
34203
|
}
|
|
34204
34204
|
}, { relatedRequestId, resumptionToken, onresumptiontoken }).catch((error63) => this._onerror(new Error(`Failed to send cancellation: ${error63}`)));
|
|
34205
|
-
const error62 =
|
|
34205
|
+
const error62 = reason2 instanceof McpError ? reason2 : new McpError(ErrorCode.RequestTimeout, String(reason2));
|
|
34206
34206
|
reject(error62);
|
|
34207
34207
|
};
|
|
34208
34208
|
this._responseHandlers.set(messageId, (response) => {
|
|
@@ -36349,13 +36349,13 @@ function fromBase64Url(code) {
|
|
|
36349
36349
|
function findConnect(pasted) {
|
|
36350
36350
|
for (const [, body] of pasted.matchAll(/symc_([A-Za-z0-9_-]{20,})/g)) {
|
|
36351
36351
|
try {
|
|
36352
|
-
const
|
|
36353
|
-
if (typeof
|
|
36352
|
+
const connect3 = JSON.parse(fromBase64Url(body));
|
|
36353
|
+
if (typeof connect3?.api === "string" && typeof connect3?.id === "string" && typeof connect3?.secret === "string") {
|
|
36354
36354
|
return {
|
|
36355
|
-
api:
|
|
36356
|
-
id:
|
|
36357
|
-
secret:
|
|
36358
|
-
...typeof
|
|
36355
|
+
api: connect3.api,
|
|
36356
|
+
id: connect3.id,
|
|
36357
|
+
secret: connect3.secret,
|
|
36358
|
+
...typeof connect3.space === "string" ? { space: connect3.space } : {}
|
|
36359
36359
|
};
|
|
36360
36360
|
}
|
|
36361
36361
|
} catch {
|
|
@@ -36545,19 +36545,36 @@ async function call(url2, init) {
|
|
|
36545
36545
|
}
|
|
36546
36546
|
return resp.status === 204 ? void 0 : await resp.json();
|
|
36547
36547
|
}
|
|
36548
|
-
function redeemConnect(
|
|
36549
|
-
return call(`${
|
|
36548
|
+
function redeemConnect(connect3, about) {
|
|
36549
|
+
return call(`${connect3.api}/agents/claim`, {
|
|
36550
36550
|
method: "POST",
|
|
36551
|
-
body: { id:
|
|
36551
|
+
body: { id: connect3.id, secret: connect3.secret, ...about }
|
|
36552
36552
|
});
|
|
36553
36553
|
}
|
|
36554
|
-
|
|
36554
|
+
async function redeemConnectFor(connect3, agents) {
|
|
36555
|
+
const connected = await call(`${connect3.api}/agents/claim`, {
|
|
36556
|
+
method: "POST",
|
|
36557
|
+
body: { id: connect3.id, secret: connect3.secret, agents }
|
|
36558
|
+
});
|
|
36559
|
+
if (connected.all?.length !== agents.length) {
|
|
36560
|
+
throw new Error("This Symposium site cannot connect several agents with one code yet.");
|
|
36561
|
+
}
|
|
36562
|
+
return { person: connected.person, all: connected.all };
|
|
36563
|
+
}
|
|
36564
|
+
var AccountClient = class _AccountClient {
|
|
36555
36565
|
constructor(api, token) {
|
|
36556
36566
|
this.api = api;
|
|
36557
36567
|
this.token = token;
|
|
36558
36568
|
}
|
|
36559
36569
|
api;
|
|
36560
36570
|
token;
|
|
36571
|
+
/**
|
|
36572
|
+
* The same credential at another site. Only for a site that shares this one's
|
|
36573
|
+
* accounts, which is what a summons naming it (`Summons.api`) says.
|
|
36574
|
+
*/
|
|
36575
|
+
at(api) {
|
|
36576
|
+
return api === this.api ? this : new _AccountClient(api, this.token);
|
|
36577
|
+
}
|
|
36561
36578
|
/** The spaces this agent's person owns or is a member of, most recently active first. */
|
|
36562
36579
|
async spaces() {
|
|
36563
36580
|
return (await call(`${this.api}/agents/me/spaces`, {
|
|
@@ -36606,6 +36623,18 @@ var AccountClient = class {
|
|
|
36606
36623
|
}
|
|
36607
36624
|
}
|
|
36608
36625
|
};
|
|
36626
|
+
function summonsApi(summons, fallback) {
|
|
36627
|
+
if (!summons.api) return fallback;
|
|
36628
|
+
try {
|
|
36629
|
+
const url2 = new URL(summons.api);
|
|
36630
|
+
const local = /^(localhost|127\.0\.0\.1|\[::1\]|.+\.localhost)$/.test(url2.hostname);
|
|
36631
|
+
if (url2.protocol !== "https:" && !(url2.protocol === "http:" && local)) return fallback;
|
|
36632
|
+
if (url2.username || url2.password || url2.search || url2.hash) return fallback;
|
|
36633
|
+
return url2.href.replace(/\/$/, "");
|
|
36634
|
+
} catch {
|
|
36635
|
+
return fallback;
|
|
36636
|
+
}
|
|
36637
|
+
}
|
|
36609
36638
|
var SUMMONS_FRESH_MS = 10 * 60 * 1e3;
|
|
36610
36639
|
function chooseSpace(spaces, wanted, summoned) {
|
|
36611
36640
|
const query = wanted?.trim().toLowerCase();
|
|
@@ -36633,6 +36662,8 @@ var SpaceClient = class {
|
|
|
36633
36662
|
space;
|
|
36634
36663
|
/** Origin of the Symposium app this space lives on. */
|
|
36635
36664
|
appOrigin;
|
|
36665
|
+
/** Base URL of that app's REST API, as the invite gave it (no trailing slash). */
|
|
36666
|
+
api;
|
|
36636
36667
|
base;
|
|
36637
36668
|
key;
|
|
36638
36669
|
cursor;
|
|
@@ -36641,10 +36672,11 @@ var SpaceClient = class {
|
|
|
36641
36672
|
this.space = space2;
|
|
36642
36673
|
this.key = key;
|
|
36643
36674
|
this.appOrigin = new URL(api).origin;
|
|
36644
|
-
this.
|
|
36675
|
+
this.api = api.replace(/\/$/, "");
|
|
36676
|
+
this.base = `${this.api}/spaces/${encodeURIComponent(space2)}`;
|
|
36645
36677
|
}
|
|
36646
|
-
async request(method,
|
|
36647
|
-
const resp = await fetch(this.base +
|
|
36678
|
+
async request(method, path4, body, timeoutMs = 3e4) {
|
|
36679
|
+
const resp = await fetch(this.base + path4, {
|
|
36648
36680
|
method,
|
|
36649
36681
|
headers: {
|
|
36650
36682
|
Authorization: `Bearer ${this.key}`,
|
|
@@ -36652,11 +36684,16 @@ var SpaceClient = class {
|
|
|
36652
36684
|
},
|
|
36653
36685
|
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
36654
36686
|
signal: AbortSignal.timeout(timeoutMs)
|
|
36687
|
+
}).catch((e) => {
|
|
36688
|
+
const why = e instanceof Error ? e.message : String(e);
|
|
36689
|
+
throw new Error(
|
|
36690
|
+
`Could not reach ${this.appOrigin} (${why}). If that site is no longer running, call join_space to join again.`
|
|
36691
|
+
);
|
|
36655
36692
|
});
|
|
36656
36693
|
if (!resp.ok) {
|
|
36657
36694
|
const detail = await resp.json().catch(() => null);
|
|
36658
36695
|
throw new Error(
|
|
36659
|
-
`${method} ${
|
|
36696
|
+
`${method} ${path4} failed (${resp.status}): ${detail?.error ?? resp.statusText}`
|
|
36660
36697
|
);
|
|
36661
36698
|
}
|
|
36662
36699
|
return resp.status === 204 ? void 0 : await resp.json();
|
|
@@ -36721,7 +36758,7 @@ var SpaceClient = class {
|
|
|
36721
36758
|
// --- proposals -------------------------------------------------------------------
|
|
36722
36759
|
/**
|
|
36723
36760
|
* Offer to do something and wait for a person to approve. Shows as a card in the
|
|
36724
|
-
*
|
|
36761
|
+
* conversation. Do not act until a `proposal_decided` event arrives with this
|
|
36725
36762
|
* proposal's `seq` and decision `approved`.
|
|
36726
36763
|
*/
|
|
36727
36764
|
propose(text2, options = {}) {
|
|
@@ -36733,9 +36770,11 @@ var SpaceClient = class {
|
|
|
36733
36770
|
}
|
|
36734
36771
|
// --- attention -------------------------------------------------------------------
|
|
36735
36772
|
/**
|
|
36736
|
-
* Bring everyone's stage to one tab: a surface (yours or anyone's), or
|
|
36737
|
-
*
|
|
36738
|
-
*
|
|
36773
|
+
* Bring everyone's stage to one tab: a surface (yours or anyone's), the document or
|
|
36774
|
+
* Agents. People are switched to it, with a note saying you did it and a way to go
|
|
36775
|
+
* back. Use it when you have put up something everyone should look at now. The
|
|
36776
|
+
* conversation is a column beside the stage, not a tab: that target opens it for
|
|
36777
|
+
* anyone who has closed it.
|
|
36739
36778
|
*/
|
|
36740
36779
|
bringEveryoneTo(target) {
|
|
36741
36780
|
return this.request("POST", "/focus", { target });
|
|
@@ -36852,6 +36891,14 @@ function agentName(client2, configured = process.env.SYMPOSIUM_AGENT_NAME) {
|
|
|
36852
36891
|
return words.map((w) => w[0].toUpperCase() + w.slice(1)).join(" ") || "Agent";
|
|
36853
36892
|
}
|
|
36854
36893
|
|
|
36894
|
+
// src/setup.ts
|
|
36895
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
36896
|
+
import { existsSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
36897
|
+
import { homedir as homedir2, hostname as hostname3 } from "node:os";
|
|
36898
|
+
import path2 from "node:path";
|
|
36899
|
+
import { createInterface } from "node:readline/promises";
|
|
36900
|
+
import { fileURLToPath } from "node:url";
|
|
36901
|
+
|
|
36855
36902
|
// src/remembered.ts
|
|
36856
36903
|
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
36857
36904
|
import { homedir } from "node:os";
|
|
@@ -36897,12 +36944,360 @@ function startingInvite(configured = process.env.SYMPOSIUM_INVITE) {
|
|
|
36897
36944
|
function rememberInvite(invite, configured = process.env.SYMPOSIUM_INVITE) {
|
|
36898
36945
|
return save({ ...load(), invite, configured: configured ?? "" });
|
|
36899
36946
|
}
|
|
36900
|
-
|
|
36901
|
-
const account2 =
|
|
36902
|
-
return typeof account2?.api === "string" && typeof account2?.token === "string"
|
|
36947
|
+
var isAccount = (value) => {
|
|
36948
|
+
const account2 = value;
|
|
36949
|
+
return typeof account2?.api === "string" && typeof account2?.token === "string";
|
|
36950
|
+
};
|
|
36951
|
+
var accounts = (saved) => [saved.account, ...Array.isArray(saved.others) ? saved.others : []].filter(isAccount);
|
|
36952
|
+
function savedAccount(api) {
|
|
36953
|
+
const all = accounts(load());
|
|
36954
|
+
return api && all.find((a) => a.api === api) || all[0];
|
|
36903
36955
|
}
|
|
36904
36956
|
function rememberAccount(account2) {
|
|
36905
|
-
|
|
36957
|
+
const saved = load();
|
|
36958
|
+
if (!account2) return save({ ...saved, account: void 0, others: void 0 });
|
|
36959
|
+
const [first, ...rest] = accounts(saved);
|
|
36960
|
+
if (!first || first.api === account2.api) return save({ ...saved, account: account2, others: rest });
|
|
36961
|
+
return save({
|
|
36962
|
+
...saved,
|
|
36963
|
+
account: first,
|
|
36964
|
+
others: [...rest.filter((a) => a.api !== account2.api), account2]
|
|
36965
|
+
});
|
|
36966
|
+
}
|
|
36967
|
+
|
|
36968
|
+
// src/setup.ts
|
|
36969
|
+
var SERVER = "symposium";
|
|
36970
|
+
var realHost = {
|
|
36971
|
+
run(command, args) {
|
|
36972
|
+
const done = spawnSync(command, args, {
|
|
36973
|
+
stdio: "ignore",
|
|
36974
|
+
shell: process.platform === "win32",
|
|
36975
|
+
timeout: 6e4
|
|
36976
|
+
});
|
|
36977
|
+
return done.error ? void 0 : done.status ?? 1;
|
|
36978
|
+
},
|
|
36979
|
+
redeem: redeemConnectFor,
|
|
36980
|
+
join: (account2, space2) => new AccountClient(account2.api, account2.token).join(space2),
|
|
36981
|
+
say: (line) => void process.stdout.write(`${line}
|
|
36982
|
+
`),
|
|
36983
|
+
async ask(question) {
|
|
36984
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) return void 0;
|
|
36985
|
+
const prompt = createInterface({ input: process.stdin, output: process.stdout });
|
|
36986
|
+
const gone = new Promise(
|
|
36987
|
+
(resolve) => prompt.once("close", () => resolve(void 0))
|
|
36988
|
+
);
|
|
36989
|
+
try {
|
|
36990
|
+
const answer = await Promise.race([prompt.question(question), gone]).catch(() => void 0);
|
|
36991
|
+
if (answer === void 0) process.stdout.write("\n");
|
|
36992
|
+
return answer;
|
|
36993
|
+
} finally {
|
|
36994
|
+
prompt.close();
|
|
36995
|
+
}
|
|
36996
|
+
},
|
|
36997
|
+
open(command, args) {
|
|
36998
|
+
return new Promise((resolve) => {
|
|
36999
|
+
const ignore = () => {
|
|
37000
|
+
};
|
|
37001
|
+
process.on("SIGINT", ignore);
|
|
37002
|
+
const child = spawn(command, args, {
|
|
37003
|
+
stdio: "inherit",
|
|
37004
|
+
shell: process.platform === "win32"
|
|
37005
|
+
});
|
|
37006
|
+
const done = () => {
|
|
37007
|
+
process.off("SIGINT", ignore);
|
|
37008
|
+
resolve();
|
|
37009
|
+
};
|
|
37010
|
+
child.on("error", done);
|
|
37011
|
+
child.on("exit", done);
|
|
37012
|
+
});
|
|
37013
|
+
}
|
|
37014
|
+
};
|
|
37015
|
+
function serverCommand(self = fileURLToPath(import.meta.url)) {
|
|
37016
|
+
return self.endsWith(".ts") ? ["npx", "-y", "tsx", path2.join(path2.dirname(self), "mcp.ts")] : ["npx", "-y", "symposium-mcp"];
|
|
37017
|
+
}
|
|
37018
|
+
var withCommand = (label, agent2, bin, add) => ({
|
|
37019
|
+
label,
|
|
37020
|
+
agent: agent2,
|
|
37021
|
+
found: (host) => host.run(bin, ["--version"]) !== void 0,
|
|
37022
|
+
register(host, command) {
|
|
37023
|
+
if (host.run(bin, ["mcp", "get", SERVER]) === 0) return "present";
|
|
37024
|
+
if (host.run(bin, add(command)) !== 0) throw new Error(`\`${bin} mcp add\` failed`);
|
|
37025
|
+
return "added";
|
|
37026
|
+
},
|
|
37027
|
+
start: (prompt) => [bin, prompt]
|
|
37028
|
+
});
|
|
37029
|
+
var withConfigFile = (label, agent2, dir, file3) => ({
|
|
37030
|
+
label,
|
|
37031
|
+
agent: agent2,
|
|
37032
|
+
found: () => existsSync(path2.join(homedir2(), ...dir)),
|
|
37033
|
+
register(_host, command) {
|
|
37034
|
+
const target = path2.join(homedir2(), ...dir, file3);
|
|
37035
|
+
let settings = {};
|
|
37036
|
+
if (existsSync(target)) {
|
|
37037
|
+
let parsed;
|
|
37038
|
+
try {
|
|
37039
|
+
parsed = JSON.parse(readFileSync2(target, "utf8"));
|
|
37040
|
+
} catch {
|
|
37041
|
+
parsed = null;
|
|
37042
|
+
}
|
|
37043
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
37044
|
+
throw new Error(`${target} is not plain JSON; add the server to it by hand`);
|
|
37045
|
+
}
|
|
37046
|
+
settings = parsed;
|
|
37047
|
+
}
|
|
37048
|
+
const servers = settings.mcpServers;
|
|
37049
|
+
const existing = typeof servers === "object" && servers !== null && !Array.isArray(servers) ? servers : {};
|
|
37050
|
+
if (SERVER in existing) return "present";
|
|
37051
|
+
settings.mcpServers = {
|
|
37052
|
+
...existing,
|
|
37053
|
+
[SERVER]: {
|
|
37054
|
+
command: command[0],
|
|
37055
|
+
args: command.slice(1),
|
|
37056
|
+
env: { SYMPOSIUM_AGENT_NAME: agent2 }
|
|
37057
|
+
}
|
|
37058
|
+
};
|
|
37059
|
+
writeFileSync2(target, `${JSON.stringify(settings, null, 2)}
|
|
37060
|
+
`);
|
|
37061
|
+
return "added";
|
|
37062
|
+
}
|
|
37063
|
+
});
|
|
37064
|
+
var TARGETS = [
|
|
37065
|
+
withCommand("Claude Code", "Claude", "claude", (command) => [
|
|
37066
|
+
"mcp",
|
|
37067
|
+
"add",
|
|
37068
|
+
"--scope",
|
|
37069
|
+
"user",
|
|
37070
|
+
SERVER,
|
|
37071
|
+
"--",
|
|
37072
|
+
...command
|
|
37073
|
+
]),
|
|
37074
|
+
withCommand("Codex", "Codex", "codex", (command) => ["mcp", "add", SERVER, "--", ...command]),
|
|
37075
|
+
withConfigFile("Cursor", "Cursor", [".cursor"], "mcp.json"),
|
|
37076
|
+
withConfigFile("Gemini CLI", "Gemini", [".gemini"], "settings.json"),
|
|
37077
|
+
withConfigFile("Windsurf", "Windsurf", [".codeium", "windsurf"], "mcp_config.json")
|
|
37078
|
+
];
|
|
37079
|
+
var reason = (e) => e instanceof Error ? e.message : String(e);
|
|
37080
|
+
async function setup(argv, host = realHost) {
|
|
37081
|
+
const pasted = argv.join(" ").trim();
|
|
37082
|
+
const code = pasted ? findConnect(pasted) : void 0;
|
|
37083
|
+
if (pasted && !code) {
|
|
37084
|
+
host.say(
|
|
37085
|
+
'That is not a Symposium connect code. Press "Connect your agent" in Symposium and copy the line again.'
|
|
37086
|
+
);
|
|
37087
|
+
return 1;
|
|
37088
|
+
}
|
|
37089
|
+
const command = serverCommand();
|
|
37090
|
+
const found = TARGETS.filter((target) => target.found(host));
|
|
37091
|
+
if (found.length === 0) {
|
|
37092
|
+
host.say("No agent that setup knows was found on this computer.");
|
|
37093
|
+
host.say(`It looks for: ${TARGETS.map((t) => t.label).join(", ")}.`);
|
|
37094
|
+
host.say(`For any other MCP client, add this as a stdio server: ${command.join(" ")}`);
|
|
37095
|
+
return 1;
|
|
37096
|
+
}
|
|
37097
|
+
const rows = found.map((target) => {
|
|
37098
|
+
try {
|
|
37099
|
+
return { target, tools: target.register(host, command), note: "" };
|
|
37100
|
+
} catch (e) {
|
|
37101
|
+
return { target, tools: "failed", note: reason(e) };
|
|
37102
|
+
}
|
|
37103
|
+
});
|
|
37104
|
+
let failed = rows.some((row) => row.tools === "failed");
|
|
37105
|
+
const connection = /* @__PURE__ */ new Map();
|
|
37106
|
+
const paired = (name) => {
|
|
37107
|
+
rememberAs(name);
|
|
37108
|
+
const account2 = savedAccount(code?.api);
|
|
37109
|
+
return code && account2?.api === code.api ? account2 : void 0;
|
|
37110
|
+
};
|
|
37111
|
+
if (code) {
|
|
37112
|
+
const names = [...new Set(rows.filter((r) => r.tools !== "failed").map((r) => r.target.agent))];
|
|
37113
|
+
const fresh = names.filter((name) => !paired(name));
|
|
37114
|
+
for (const name of names) if (!fresh.includes(name)) connection.set(name, "already connected");
|
|
37115
|
+
if (fresh.length) {
|
|
37116
|
+
try {
|
|
37117
|
+
const { person, all } = await host.redeem(
|
|
37118
|
+
code,
|
|
37119
|
+
fresh.map((name) => ({ name, device: hostname3() }))
|
|
37120
|
+
);
|
|
37121
|
+
fresh.forEach((name, i) => {
|
|
37122
|
+
rememberAs(name);
|
|
37123
|
+
const saved = rememberAccount({ api: code.api, token: all[i].token, person });
|
|
37124
|
+
connection.set(
|
|
37125
|
+
name,
|
|
37126
|
+
saved ? `connected for ${person}` : "connected, but the connection could not be saved"
|
|
37127
|
+
);
|
|
37128
|
+
failed ||= !saved;
|
|
37129
|
+
});
|
|
37130
|
+
} catch (e) {
|
|
37131
|
+
failed = true;
|
|
37132
|
+
for (const name of fresh) connection.set(name, `not connected: ${reason(e)}`);
|
|
37133
|
+
}
|
|
37134
|
+
}
|
|
37135
|
+
if (code.space) {
|
|
37136
|
+
for (const name of names) {
|
|
37137
|
+
const account2 = paired(name);
|
|
37138
|
+
if (!account2) continue;
|
|
37139
|
+
try {
|
|
37140
|
+
rememberInvite(encodeInvite(await host.join(account2, code.space)));
|
|
37141
|
+
} catch (e) {
|
|
37142
|
+
connection.set(name, `${connection.get(name)}; could not join the space: ${reason(e)}`);
|
|
37143
|
+
}
|
|
37144
|
+
}
|
|
37145
|
+
}
|
|
37146
|
+
}
|
|
37147
|
+
const width = Math.max(...rows.map((row) => row.target.label.length));
|
|
37148
|
+
host.say("Symposium setup");
|
|
37149
|
+
host.say("");
|
|
37150
|
+
for (const row of rows) {
|
|
37151
|
+
const tools = row.tools === "added" ? "tools added" : row.tools === "present" ? "tools already there" : `could not add the tools: ${row.note}`;
|
|
37152
|
+
const link = row.tools === "failed" ? void 0 : connection.get(row.target.agent);
|
|
37153
|
+
host.say(` ${row.target.label.padEnd(width)} ${[tools, link].filter(Boolean).join(", ")}`);
|
|
37154
|
+
}
|
|
37155
|
+
host.say("");
|
|
37156
|
+
if (!code) {
|
|
37157
|
+
host.say('To connect them: press "Connect your agent" in Symposium and run the line it shows.');
|
|
37158
|
+
return failed ? 1 : 0;
|
|
37159
|
+
}
|
|
37160
|
+
const ready = rows.filter(
|
|
37161
|
+
(row) => row.tools !== "failed" && row.target.start && paired(row.target.agent)
|
|
37162
|
+
);
|
|
37163
|
+
const chosen = ready.length ? await choose(
|
|
37164
|
+
host,
|
|
37165
|
+
ready.map((row) => row.target)
|
|
37166
|
+
) : void 0;
|
|
37167
|
+
if (!chosen) {
|
|
37168
|
+
host.say('Start a new session in any of them and say "join my meeting".');
|
|
37169
|
+
return failed ? 1 : 0;
|
|
37170
|
+
}
|
|
37171
|
+
const [bin, ...args] = chosen.start(
|
|
37172
|
+
code.space ? `Join my Symposium meeting in the space ${code.space}.` : "Join my Symposium meeting."
|
|
37173
|
+
);
|
|
37174
|
+
await host.open(bin, args);
|
|
37175
|
+
return failed ? 1 : 0;
|
|
37176
|
+
}
|
|
37177
|
+
async function choose(host, targets) {
|
|
37178
|
+
const question = targets.length === 1 ? `Open ${targets[0].label} in the meeting now? [Y/n] ` : `Open one in the meeting now? ${targets.map((t, i) => `${i + 1}) ${t.label}`).join(" ")} [1, or n for none] `;
|
|
37179
|
+
const answer = (await host.ask(question))?.trim().toLowerCase();
|
|
37180
|
+
if (answer === void 0 || answer.startsWith("n")) return void 0;
|
|
37181
|
+
if (answer === "" || answer.startsWith("y")) return targets[0];
|
|
37182
|
+
return targets[Number(answer) - 1];
|
|
37183
|
+
}
|
|
37184
|
+
|
|
37185
|
+
// src/wake.ts
|
|
37186
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
37187
|
+
import path3 from "node:path";
|
|
37188
|
+
import { pathToFileURL } from "node:url";
|
|
37189
|
+
function wakeEvents(events, agentId, now, maxAgeMs) {
|
|
37190
|
+
return events.filter(
|
|
37191
|
+
(e) => now - e.ts <= maxAgeMs && (e.type !== "request" || e.to.includes(agentId))
|
|
37192
|
+
);
|
|
37193
|
+
}
|
|
37194
|
+
var flag = (name, fallback) => {
|
|
37195
|
+
const hit = process.argv.find((a) => a.startsWith(`--${name}=`));
|
|
37196
|
+
return hit ? hit.slice(name.length + 3) : fallback;
|
|
37197
|
+
};
|
|
37198
|
+
var minutes = (name, fallback) => {
|
|
37199
|
+
const n = Number(flag(name, String(fallback)));
|
|
37200
|
+
return Number.isFinite(n) && n > 0 ? n : fallback;
|
|
37201
|
+
};
|
|
37202
|
+
function positionFile(client2) {
|
|
37203
|
+
const safe = (s) => s.replace(/[^\w-]/g, "_");
|
|
37204
|
+
return path3.join(
|
|
37205
|
+
path3.dirname(stateFile("")),
|
|
37206
|
+
`wake-${safe(client2.space)}-${safe(client2.agentId)}`
|
|
37207
|
+
);
|
|
37208
|
+
}
|
|
37209
|
+
function readPosition(file3) {
|
|
37210
|
+
try {
|
|
37211
|
+
const n = Number(readFileSync3(file3, "utf8"));
|
|
37212
|
+
return Number.isInteger(n) && n >= 0 ? n : void 0;
|
|
37213
|
+
} catch {
|
|
37214
|
+
return void 0;
|
|
37215
|
+
}
|
|
37216
|
+
}
|
|
37217
|
+
function savePosition(file3, position) {
|
|
37218
|
+
if (position === void 0) return;
|
|
37219
|
+
try {
|
|
37220
|
+
mkdirSync2(path3.dirname(file3), { recursive: true, mode: 448 });
|
|
37221
|
+
const temp = `${file3}.${process.pid}.tmp`;
|
|
37222
|
+
writeFileSync3(temp, String(position), { mode: 384 });
|
|
37223
|
+
renameSync2(temp, file3);
|
|
37224
|
+
} catch {
|
|
37225
|
+
}
|
|
37226
|
+
}
|
|
37227
|
+
function rememberPosition(client2) {
|
|
37228
|
+
const position = client2.position;
|
|
37229
|
+
if (position === void 0) return;
|
|
37230
|
+
const file3 = positionFile(client2);
|
|
37231
|
+
if ((readPosition(file3) ?? -1) < position) savePosition(file3, position);
|
|
37232
|
+
}
|
|
37233
|
+
function withoutHeard(client2, events) {
|
|
37234
|
+
const heard = readPosition(positionFile(client2)) ?? -1;
|
|
37235
|
+
return events.filter(
|
|
37236
|
+
(e) => !(e.type === "request" && e.seq <= heard && e.to.includes(client2.agentId))
|
|
37237
|
+
);
|
|
37238
|
+
}
|
|
37239
|
+
async function connect() {
|
|
37240
|
+
const wanted = flag("space", "");
|
|
37241
|
+
if (!wanted) {
|
|
37242
|
+
const invite = startingInvite();
|
|
37243
|
+
return invite ? new SpaceClient(invite) : "No space to join: pass --space=<name or id>.";
|
|
37244
|
+
}
|
|
37245
|
+
const api = flag("api", "").replace(/\/$/, "");
|
|
37246
|
+
const saved = savedAccount(api || void 0);
|
|
37247
|
+
if (!saved) return "--space needs this agent connected to an account (use connect first).";
|
|
37248
|
+
const paired = new AccountClient(api || saved.api, saved.token);
|
|
37249
|
+
const choice = chooseSpace(await paired.spaces(), wanted);
|
|
37250
|
+
if (!("space" in choice)) {
|
|
37251
|
+
return `${choice.why} Try one of: ${choice.candidates.map((s) => s.name || s.id).join(", ")}`;
|
|
37252
|
+
}
|
|
37253
|
+
return new SpaceClient(await paired.join(choice.space.id));
|
|
37254
|
+
}
|
|
37255
|
+
async function main() {
|
|
37256
|
+
const agent2 = flag("agent", "");
|
|
37257
|
+
if (agent2) rememberAs(agent2);
|
|
37258
|
+
const client2 = await connect();
|
|
37259
|
+
if (typeof client2 === "string") {
|
|
37260
|
+
console.error(client2);
|
|
37261
|
+
return 2;
|
|
37262
|
+
}
|
|
37263
|
+
const types = flag("types", "request").split(",").filter(Boolean);
|
|
37264
|
+
const maxAgeMs = minutes("max-age-minutes", 10) * 6e4;
|
|
37265
|
+
const deadline = Date.now() + minutes("max-minutes", 30) * 6e4;
|
|
37266
|
+
const file3 = positionFile(client2);
|
|
37267
|
+
const saved = readPosition(file3);
|
|
37268
|
+
if (saved !== void 0) client2.resumeFrom(saved);
|
|
37269
|
+
console.error(`Listening for ${types.join(", ")} in space ${client2.space} as ${client2.agentId}`);
|
|
37270
|
+
let failures = 0;
|
|
37271
|
+
while (Date.now() < deadline) {
|
|
37272
|
+
try {
|
|
37273
|
+
const events = await client2.waitForEvents(deadline - Date.now(), types);
|
|
37274
|
+
failures = 0;
|
|
37275
|
+
savePosition(file3, client2.position);
|
|
37276
|
+
const wake = wakeEvents(events, client2.agentId, Date.now(), maxAgeMs);
|
|
37277
|
+
if (wake.length) {
|
|
37278
|
+
for (const event of wake) process.stdout.write(`${JSON.stringify(event)}
|
|
37279
|
+
`);
|
|
37280
|
+
return 0;
|
|
37281
|
+
}
|
|
37282
|
+
} catch (e) {
|
|
37283
|
+
if (++failures >= 5) {
|
|
37284
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
37285
|
+
return 1;
|
|
37286
|
+
}
|
|
37287
|
+
await new Promise((resolve) => setTimeout(resolve, 2e3 * failures));
|
|
37288
|
+
}
|
|
37289
|
+
}
|
|
37290
|
+
console.error("Nothing arrived in time; start it again.");
|
|
37291
|
+
return 3;
|
|
37292
|
+
}
|
|
37293
|
+
if (process.argv[1] && path3.basename(process.argv[1]).startsWith("wake.") && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
37294
|
+
main().then(
|
|
37295
|
+
(code) => process.exit(code),
|
|
37296
|
+
(e) => {
|
|
37297
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
37298
|
+
process.exit(1);
|
|
37299
|
+
}
|
|
37300
|
+
);
|
|
36906
37301
|
}
|
|
36907
37302
|
|
|
36908
37303
|
// src/mcp.ts
|
|
@@ -36973,6 +37368,14 @@ away. Do not run it as a shell command, and do not change the MCP configuration:
|
|
|
36973
37368
|
switches spaces and remembers the choice for later sessions by itself. Then tell them in a
|
|
36974
37369
|
line that you are in, and stay engaged by calling wait_for_events in a loop.
|
|
36975
37370
|
|
|
37371
|
+
People in the space can only reach you while you are waiting on it: the room shows you as
|
|
37372
|
+
"not listening" otherwise, and what they ask goes unheard. join_space tells you two things
|
|
37373
|
+
about that; act on both. \`unansweredRequests\` are requests sent to you while nobody was
|
|
37374
|
+
waiting. \`listen.command\` is a shell command that waits for you in the background and
|
|
37375
|
+
exits when a request arrives: if you can run background commands, start it whenever you
|
|
37376
|
+
stop calling wait_for_events (before ending your turn, or before a long piece of other
|
|
37377
|
+
work), and start it again after handling what it printed.
|
|
37378
|
+
|
|
36976
37379
|
When the user pastes a connect code \u2014 a line like "Connect to my Symposium: symc_\u2026" \u2014
|
|
36977
37380
|
call connect with what they pasted, straight away. It links you to their account for
|
|
36978
37381
|
good (and joins the space it was made in, if any).
|
|
@@ -36995,7 +37398,7 @@ function start() {
|
|
|
36995
37398
|
var client;
|
|
36996
37399
|
function account() {
|
|
36997
37400
|
begin();
|
|
36998
|
-
const saved = savedAccount();
|
|
37401
|
+
const saved = savedAccount(client?.api);
|
|
36999
37402
|
return saved ? new AccountClient(saved.api, saved.token) : void 0;
|
|
37000
37403
|
}
|
|
37001
37404
|
var NOT_PAIRED = `You are not connected to the user's Symposium account. Ask them to press "Connect your agent" in Symposium (on the home page, or Stage \u2192 Agents in a space) and paste you the line it shows; then call connect with it. It is needed once.`;
|
|
@@ -37018,13 +37421,42 @@ var markSeen = (at) => {
|
|
|
37018
37421
|
summonsSeen = Math.max(summonsSeen ?? 0, at);
|
|
37019
37422
|
};
|
|
37020
37423
|
var summonsFollowed = 0;
|
|
37424
|
+
var lastSummons;
|
|
37425
|
+
async function joinWhereCalled(paired, space2, called) {
|
|
37426
|
+
const site = called?.space === space2 ? summonsApi(called, paired.api) : paired.api;
|
|
37427
|
+
if (site === paired.api) return paired.join(space2);
|
|
37428
|
+
return paired.at(site).join(space2).catch(() => paired.join(space2));
|
|
37429
|
+
}
|
|
37430
|
+
var quoteArg = (arg) => /^[\w@%+=:,./-]+$/.test(arg) ? arg : `'${arg.replace(/'/g, `'\\''`)}'`;
|
|
37431
|
+
function wakeCommand(target) {
|
|
37432
|
+
const script = process.argv[1];
|
|
37433
|
+
if (!script) return void 0;
|
|
37434
|
+
const where = savedAccount(target.api)?.token.startsWith(PAIRED_KEY_PREFIX) ? [`--space=${target.space}`, `--api=${target.api}`, `--agent=${whoAmI()}`] : [];
|
|
37435
|
+
return [process.execPath, ...process.execArgv, script, "wake", ...where].map(quoteArg).join(" ");
|
|
37436
|
+
}
|
|
37437
|
+
var UNANSWERED_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
37021
37438
|
var unfinishedWait;
|
|
37022
37439
|
async function enter(next, invite) {
|
|
37023
37440
|
begin();
|
|
37024
|
-
const
|
|
37441
|
+
const same = client?.space === next.space && client.agentId === next.agentId;
|
|
37442
|
+
const target = same && client.api === next.api ? client : next;
|
|
37443
|
+
if (same && target === next && client.position !== void 0) {
|
|
37444
|
+
next.resumeFrom(client.position);
|
|
37445
|
+
}
|
|
37025
37446
|
const snap = await target.snapshot();
|
|
37026
37447
|
client = target;
|
|
37448
|
+
rememberPosition(target);
|
|
37027
37449
|
const remembered = rememberInvite(invite);
|
|
37450
|
+
const now = Date.now();
|
|
37451
|
+
const unanswered = snap.timeline.filter(
|
|
37452
|
+
(e) => e.kind === "request" && now - e.ts < UNANSWERED_MAX_AGE_MS && e.request?.to.some((t) => t.agentId === target.agentId && t.status === "sent")
|
|
37453
|
+
).map((e) => ({
|
|
37454
|
+
id: e.seq,
|
|
37455
|
+
from: e.name,
|
|
37456
|
+
text: e.text,
|
|
37457
|
+
asked: `${Math.max(0, Math.round((now - e.ts) / 6e4))} min ago`
|
|
37458
|
+
}));
|
|
37459
|
+
const command = wakeCommand(target);
|
|
37028
37460
|
return {
|
|
37029
37461
|
space: snap.id,
|
|
37030
37462
|
...snap.name ? { name: snap.name } : {},
|
|
@@ -37032,6 +37464,14 @@ async function enter(next, invite) {
|
|
|
37032
37464
|
liveParticipants: snap.participants,
|
|
37033
37465
|
presence: snap.presence,
|
|
37034
37466
|
surfaces: snap.surfaces.map((s) => ({ id: s.id, title: s.title })),
|
|
37467
|
+
...unanswered.length ? {
|
|
37468
|
+
unansweredRequests: unanswered,
|
|
37469
|
+
aboutUnanswered: 'Sent to you while you were not listening, and still showing as unanswered in the room. For a recent one, call update_request with "acknowledged" and do it. For one that is hours old or plainly overtaken, ask the user whether they still want it, and otherwise close it with update_request "failed" and a note saying it was not picked up in time.'
|
|
37470
|
+
} : {},
|
|
37471
|
+
listen: {
|
|
37472
|
+
how: "People can reach you only while you are waiting: call wait_for_events in a loop. When you stop (before ending your turn, or before long work elsewhere) and you can run a shell command in the background, start `command` there: it exits as soon as a request for you arrives and prints it. Handle it, then start the command again.",
|
|
37473
|
+
...command ? { command } : {}
|
|
37474
|
+
},
|
|
37035
37475
|
remembered: remembered ? "Later sessions start in this space. No configuration change is needed." : "Could not save this space; later sessions will need to join again."
|
|
37036
37476
|
};
|
|
37037
37477
|
}
|
|
@@ -37064,9 +37504,35 @@ function begin() {
|
|
|
37064
37504
|
client = start();
|
|
37065
37505
|
}
|
|
37066
37506
|
server.server.oninitialized = begin;
|
|
37067
|
-
async function
|
|
37507
|
+
async function connect2(code) {
|
|
37068
37508
|
begin();
|
|
37069
|
-
const
|
|
37509
|
+
const known = savedAccount(code.api);
|
|
37510
|
+
if (known && known.api !== code.api) {
|
|
37511
|
+
const there = new AccountClient(code.api, known.token);
|
|
37512
|
+
const shared = await there.spaces().then(
|
|
37513
|
+
() => true,
|
|
37514
|
+
() => false
|
|
37515
|
+
);
|
|
37516
|
+
if (shared) {
|
|
37517
|
+
const result2 = {
|
|
37518
|
+
connected: true,
|
|
37519
|
+
with: known.person,
|
|
37520
|
+
note: `This site shares its accounts with ${new URL(known.api).origin}, where you were already connected. That connection is used here too; the code was not needed.`
|
|
37521
|
+
};
|
|
37522
|
+
if (!code.space) {
|
|
37523
|
+
return text({
|
|
37524
|
+
...result2,
|
|
37525
|
+
next: "Call join_space to join the meeting the user is in, or list_spaces to see their spaces."
|
|
37526
|
+
});
|
|
37527
|
+
}
|
|
37528
|
+
const joined2 = await there.join(code.space);
|
|
37529
|
+
return text({
|
|
37530
|
+
...result2,
|
|
37531
|
+
joined: await enter(new SpaceClient(joined2), encodeInvite(joined2))
|
|
37532
|
+
});
|
|
37533
|
+
}
|
|
37534
|
+
}
|
|
37535
|
+
const connected = await redeemConnect(code, { name: whoAmI(), device: hostname4() });
|
|
37070
37536
|
const saved = rememberAccount({
|
|
37071
37537
|
api: code.api,
|
|
37072
37538
|
token: connected.token,
|
|
@@ -37105,7 +37571,7 @@ server.registerTool(
|
|
|
37105
37571
|
'There is no Symposium connect code in that text. Ask the user to press "Connect your agent" in Symposium and copy the line again.'
|
|
37106
37572
|
);
|
|
37107
37573
|
}
|
|
37108
|
-
return
|
|
37574
|
+
return connect2(code);
|
|
37109
37575
|
}
|
|
37110
37576
|
);
|
|
37111
37577
|
server.registerTool(
|
|
@@ -37142,7 +37608,7 @@ server.registerTool(
|
|
|
37142
37608
|
const invite = pasted ? findInvite(pasted) : void 0;
|
|
37143
37609
|
if (invite) return text(await enter(new SpaceClient(invite), invite));
|
|
37144
37610
|
const code = pasted ? findConnect(pasted) : void 0;
|
|
37145
|
-
if (code) return
|
|
37611
|
+
if (code) return connect2(code);
|
|
37146
37612
|
const paired = account();
|
|
37147
37613
|
if (!paired) {
|
|
37148
37614
|
throw new Error(
|
|
@@ -37151,11 +37617,14 @@ server.registerTool(
|
|
|
37151
37617
|
}
|
|
37152
37618
|
const named = wanted ?? pasted;
|
|
37153
37619
|
const recentSummons = async () => {
|
|
37154
|
-
const
|
|
37155
|
-
return paired.summons(Math.max(
|
|
37620
|
+
const fresh2 = await paired.serverNow() - SUMMONS_FRESH_MS;
|
|
37621
|
+
return paired.summons(Math.max(fresh2, summonsFollowed));
|
|
37156
37622
|
};
|
|
37157
37623
|
const [spaces, called] = await Promise.all([paired.spaces(), named ? null : recentSummons()]);
|
|
37158
|
-
if (called)
|
|
37624
|
+
if (called) {
|
|
37625
|
+
markSeen(called.at);
|
|
37626
|
+
lastSummons = called;
|
|
37627
|
+
}
|
|
37159
37628
|
const choice = chooseSpace(spaces, named, called?.space);
|
|
37160
37629
|
if ("candidates" in choice) {
|
|
37161
37630
|
return text({
|
|
@@ -37171,7 +37640,8 @@ server.registerTool(
|
|
|
37171
37640
|
}
|
|
37172
37641
|
const target = choice.space.id;
|
|
37173
37642
|
if (called?.space === target) summonsFollowed = called.at;
|
|
37174
|
-
const
|
|
37643
|
+
const fresh = !choice.space.here && lastSummons?.space === target && await paired.serverNow() - lastSummons.at < SUMMONS_FRESH_MS;
|
|
37644
|
+
const joined = await joinWhereCalled(paired, target, fresh ? lastSummons : null);
|
|
37175
37645
|
return text(await enter(new SpaceClient(joined), encodeInvite(joined)));
|
|
37176
37646
|
}
|
|
37177
37647
|
);
|
|
@@ -37368,9 +37838,11 @@ server.registerTool(
|
|
|
37368
37838
|
const paired = account();
|
|
37369
37839
|
const summoned = (called2) => {
|
|
37370
37840
|
markSeen(called2.at);
|
|
37841
|
+
lastSummons = called2;
|
|
37842
|
+
const here = called2.space === client?.space && summonsApi(called2, client.api) === client.api;
|
|
37371
37843
|
return text({
|
|
37372
37844
|
summons: { space: called2.space, ...called2.name ? { name: called2.name } : {} },
|
|
37373
|
-
do:
|
|
37845
|
+
do: here ? 'The user pressed "Bring my agent" in the space you are already in. Tell them you are here.' : `The user pressed "Bring my agent" in ${called2.space === client?.space ? "this space on another site" : "another space"}. Call join_space with space "${called2.space}" now.`
|
|
37374
37846
|
});
|
|
37375
37847
|
};
|
|
37376
37848
|
if (!client && paired) {
|
|
@@ -37378,7 +37850,11 @@ server.registerTool(
|
|
|
37378
37850
|
return called2 ? summoned(called2) : text("(no events)");
|
|
37379
37851
|
}
|
|
37380
37852
|
const current = space();
|
|
37381
|
-
const events = unfinishedWait?.client === current ? unfinishedWait.events : current.waitForEvents(timeoutMs, types)
|
|
37853
|
+
const events = unfinishedWait?.client === current ? unfinishedWait.events : current.waitForEvents(timeoutMs, types).then((list) => {
|
|
37854
|
+
const unheard = withoutHeard(current, list);
|
|
37855
|
+
rememberPosition(current);
|
|
37856
|
+
return unheard;
|
|
37857
|
+
});
|
|
37382
37858
|
unfinishedWait = void 0;
|
|
37383
37859
|
if (!paired) {
|
|
37384
37860
|
const list = await events;
|
|
@@ -37412,9 +37888,9 @@ server.registerTool(
|
|
|
37412
37888
|
server.registerTool(
|
|
37413
37889
|
"bring_everyone_to",
|
|
37414
37890
|
{
|
|
37415
|
-
description: "Switch everyone's stage to one tab so they are all looking at the same thing: one surface (pass its id; it can be yours or another agent's)
|
|
37891
|
+
description: "Switch everyone's stage to one tab so they are all looking at the same thing: one surface (pass its id; it can be yours or another agent's) or the shared document. Each person sees a note saying you brought them there and can go back. With neither, it opens the conversation column for anyone who has closed it; the conversation is beside the stage, not a tab on it, so this is rarely needed. Use it sparingly: when you have just put up or updated something the whole room should look at now, or someone asked you to show something. Do not use it for every small update.",
|
|
37416
37892
|
inputSchema: {
|
|
37417
|
-
surfaceId: external_exports.string().optional().describe("Surface to show; omit
|
|
37893
|
+
surfaceId: external_exports.string().optional().describe("Surface to show; omit to open the conversation"),
|
|
37418
37894
|
document: external_exports.boolean().optional().describe("Set true to show the shared document instead")
|
|
37419
37895
|
}
|
|
37420
37896
|
},
|
|
@@ -37459,7 +37935,7 @@ server.registerTool(
|
|
|
37459
37935
|
server.registerTool(
|
|
37460
37936
|
"propose",
|
|
37461
37937
|
{
|
|
37462
|
-
description: 'Offer to do something and wait for a person to approve it. Everyone sees it as a card in the
|
|
37938
|
+
description: 'Offer to do something and wait for a person to approve it. Everyone sees it as a card in the conversation with an approve button and Dismiss. Use it before anything that reaches outside the meeting or is hard to undo (sending a message, filing a ticket, changing files, spending money), and when you have a suggestion the room may not want ("Add this to the decisions list?"). Do NOT act until you receive a `proposal_decided` event from wait_for_events with this proposal\'s id and decision "approved"; if it is "dismissed", drop it. After approval, report progress with update_proposal.',
|
|
37463
37939
|
inputSchema: {
|
|
37464
37940
|
text: external_exports.string().describe("What you are offering to do, as a question or a plain statement"),
|
|
37465
37941
|
action: external_exports.string().max(40).optional().describe('Label for the approve button (default "Do it")'),
|
|
@@ -37503,4 +37979,20 @@ server.registerTool(
|
|
|
37503
37979
|
return text({ request: entry.seq, status, to: entry.request?.to });
|
|
37504
37980
|
}
|
|
37505
37981
|
);
|
|
37506
|
-
|
|
37982
|
+
if (process.argv[2] === "setup") {
|
|
37983
|
+
process.exit(
|
|
37984
|
+
await setup(process.argv.slice(3)).catch((e) => {
|
|
37985
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
37986
|
+
return 1;
|
|
37987
|
+
})
|
|
37988
|
+
);
|
|
37989
|
+
} else if (process.argv[2] === "wake") {
|
|
37990
|
+
process.exit(
|
|
37991
|
+
await main().catch((e) => {
|
|
37992
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
37993
|
+
return 1;
|
|
37994
|
+
})
|
|
37995
|
+
);
|
|
37996
|
+
} else {
|
|
37997
|
+
await server.connect(new StdioServerTransport());
|
|
37998
|
+
}
|
package/package.json
CHANGED