@rallycry/conveyor-mcp 5.0.2 → 5.0.4
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 +6 -0
- package/dist/chunk-5KU3QS6A.js +45 -0
- package/dist/{chunk-XLDG5QEX.js → chunk-EUCQ26F7.js} +32 -3
- package/dist/cli.js +889 -849
- package/dist/{connection-B7CwszOV.d.ts → connection-jZ8nSRwk.d.ts} +20 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/tunnel-cli.js +5 -3
- package/dist/tunnel.d.ts +2 -1
- package/dist/wait-cli.js +5 -3
- package/dist/wait-runner.d.ts +2 -1
- package/dist/wait.d.ts +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -76,6 +76,12 @@ CONVEYOR_PROJECT_ID = "<project-id>"
|
|
|
76
76
|
| `CONVEYOR_PROJECT_ID` | no | Default project for unqualified tools. Omit it for multi-project mode. |
|
|
77
77
|
| `CONVEYOR_SUBPROJECT_ID` | no | Default board. Unqualified `create_task`/`list_tasks`/`search_tasks` target it. |
|
|
78
78
|
|
|
79
|
+
## How the server exits
|
|
80
|
+
|
|
81
|
+
The server's lifetime is its stdin pipe. It exits when stdin reaches EOF, on
|
|
82
|
+
`SIGINT` or `SIGTERM`, and when its parent process changes — so a client that
|
|
83
|
+
force-quits, crashes, or is torn down never leaves a server behind.
|
|
84
|
+
|
|
79
85
|
## Why `npx -y …@latest`
|
|
80
86
|
|
|
81
87
|
Every form above launches the server through
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// src/lifecycle.ts
|
|
2
|
+
function watchForClientExit(onExit, deps = {}) {
|
|
3
|
+
const stdin = deps.stdin ?? process.stdin;
|
|
4
|
+
const signals = deps.signals ?? process;
|
|
5
|
+
const getPpid = deps.getPpid ?? (() => process.ppid);
|
|
6
|
+
const intervalMs = deps.intervalMs ?? 3e4;
|
|
7
|
+
const watchStdin = deps.watchStdin ?? true;
|
|
8
|
+
let fired = false;
|
|
9
|
+
const fire = () => {
|
|
10
|
+
if (fired) return;
|
|
11
|
+
fired = true;
|
|
12
|
+
onExit();
|
|
13
|
+
};
|
|
14
|
+
signals.on("SIGINT", fire);
|
|
15
|
+
signals.on("SIGTERM", fire);
|
|
16
|
+
signals.on("SIGHUP", fire);
|
|
17
|
+
if (watchStdin && stdin.isTTY !== true) {
|
|
18
|
+
if (deps.resumeStdin) stdin.resume?.();
|
|
19
|
+
stdin.on("end", fire);
|
|
20
|
+
stdin.on("close", fire);
|
|
21
|
+
}
|
|
22
|
+
const initialPpid = getPpid();
|
|
23
|
+
const orphanCheck = setInterval(() => {
|
|
24
|
+
if (getPpid() !== initialPpid) fire();
|
|
25
|
+
}, intervalMs);
|
|
26
|
+
orphanCheck.unref?.();
|
|
27
|
+
return fire;
|
|
28
|
+
}
|
|
29
|
+
function installLifecycle(conn, deps = {}) {
|
|
30
|
+
const exit = deps.exit ?? ((code) => process.exit(code));
|
|
31
|
+
let stopping = false;
|
|
32
|
+
function stop(code = 0) {
|
|
33
|
+
if (stopping) return;
|
|
34
|
+
stopping = true;
|
|
35
|
+
conn.disconnect();
|
|
36
|
+
exit(code);
|
|
37
|
+
}
|
|
38
|
+
watchForClientExit(() => stop(), deps);
|
|
39
|
+
return stop;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export {
|
|
43
|
+
watchForClientExit,
|
|
44
|
+
installLifecycle
|
|
45
|
+
};
|
|
@@ -49,10 +49,14 @@ function enrichToolError(error, fallback) {
|
|
|
49
49
|
var ConveyorConnection = class {
|
|
50
50
|
socket = null;
|
|
51
51
|
config;
|
|
52
|
+
socketFactory;
|
|
52
53
|
/** project slug → id, for resolving `<project>/<card>` card paths. */
|
|
53
54
|
projectSlugIds = /* @__PURE__ */ new Map();
|
|
54
|
-
|
|
55
|
+
/** Session rooms this connection has joined, replayed after every reconnect. */
|
|
56
|
+
subscribedSessionIds = /* @__PURE__ */ new Set();
|
|
57
|
+
constructor(config, socketFactory = io) {
|
|
55
58
|
this.config = config;
|
|
59
|
+
this.socketFactory = socketFactory;
|
|
56
60
|
}
|
|
57
61
|
get projectId() {
|
|
58
62
|
return this.resolveProjectId();
|
|
@@ -86,12 +90,32 @@ var ConveyorConnection = class {
|
|
|
86
90
|
if (Array.isArray(record.data)) return record.data;
|
|
87
91
|
return [];
|
|
88
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Re-join every room this connection needs.
|
|
95
|
+
*
|
|
96
|
+
* Runs on the FIRST connect and on every reconnect. A socket.io reconnect
|
|
97
|
+
* starts a fresh server-side socket that belongs to no rooms, and Cloud Run
|
|
98
|
+
* cuts each websocket at its 60-minute request limit, so a subscribe sent
|
|
99
|
+
* only once left long-lived MCP connections silently outside the project room
|
|
100
|
+
* for the rest of the day.
|
|
101
|
+
*/
|
|
102
|
+
rejoinRooms() {
|
|
103
|
+
const socket = this.socket;
|
|
104
|
+
if (!socket) return;
|
|
105
|
+
socket.emit("projectService:subscribe", { id: this.config.projectId });
|
|
106
|
+
for (const sessionId of this.subscribedSessionIds) {
|
|
107
|
+
socket.emit("agentSessionService:subscribe", {
|
|
108
|
+
entryId: sessionId,
|
|
109
|
+
requiredLevel: "Read"
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
89
113
|
connect() {
|
|
90
114
|
return new Promise((resolve, reject) => {
|
|
91
115
|
let settled = false;
|
|
92
116
|
let attempts = 0;
|
|
93
117
|
const maxAttempts = 15;
|
|
94
|
-
this.socket =
|
|
118
|
+
this.socket = this.socketFactory(
|
|
95
119
|
this.config.apiUrl,
|
|
96
120
|
buildConveyorSocketOptions({
|
|
97
121
|
projectToken: this.config.projectToken,
|
|
@@ -99,12 +123,15 @@ var ConveyorConnection = class {
|
|
|
99
123
|
})
|
|
100
124
|
);
|
|
101
125
|
this.socket.on("connect", () => {
|
|
126
|
+
this.rejoinRooms();
|
|
102
127
|
if (!settled) {
|
|
103
128
|
settled = true;
|
|
104
|
-
this.socket?.emit("projectService:subscribe", { id: this.config.projectId });
|
|
105
129
|
resolve();
|
|
106
130
|
}
|
|
107
131
|
});
|
|
132
|
+
this.socket.on("disconnect", (reason) => {
|
|
133
|
+
if (reason === "io server disconnect") this.socket?.connect();
|
|
134
|
+
});
|
|
108
135
|
this.socket.on("connect_error", (err) => {
|
|
109
136
|
const message = err?.message ?? "unknown error";
|
|
110
137
|
if (!settled && AUTH_ERROR_RE.test(message)) {
|
|
@@ -943,6 +970,7 @@ var ConveyorConnection = class {
|
|
|
943
970
|
subscribeToSession(sessionId) {
|
|
944
971
|
const socket = this.socket;
|
|
945
972
|
if (!socket) throw new Error("Not connected");
|
|
973
|
+
this.subscribedSessionIds.add(sessionId);
|
|
946
974
|
socket.emit("agentSessionService:subscribe", {
|
|
947
975
|
entryId: sessionId,
|
|
948
976
|
requiredLevel: "Read"
|
|
@@ -1013,6 +1041,7 @@ var ConveyorConnection = class {
|
|
|
1013
1041
|
disconnect() {
|
|
1014
1042
|
this.socket?.disconnect();
|
|
1015
1043
|
this.socket = null;
|
|
1044
|
+
this.subscribedSessionIds.clear();
|
|
1016
1045
|
}
|
|
1017
1046
|
};
|
|
1018
1047
|
|