@rivus/agent 0.8.6 → 0.9.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/dist/mcp.d.ts CHANGED
@@ -40,7 +40,7 @@ declare function toControlContext(input: {
40
40
  declare const BACKGROUND_SESSION_MCP_SERVER_NAME = "rivus-background-sessions";
41
41
  declare const BACKGROUND_SESSION_MCP_SERVER_VERSION = "1.0.0";
42
42
  interface BackgroundSessionMcpServerOptions {
43
- readonly controlContext: BackgroundSessionControlContext;
43
+ readonly capability: string;
44
44
  readonly controlUrl: string;
45
45
  readonly controlToken: string;
46
46
  readonly enabledTools?: ReadonlyArray<BackgroundSessionControlCommand>;
@@ -74,6 +74,7 @@ interface BackgroundSessionControlHttpServerOptions {
74
74
  }
75
75
  interface BackgroundSessionControlHttpServer {
76
76
  port(): number;
77
+ issueCapability(context: BackgroundSessionControlContext): string;
77
78
  close(): Promise<void>;
78
79
  start(): Promise<void>;
79
80
  }
package/dist/mcp.js CHANGED
@@ -24,15 +24,14 @@ function createBackgroundSessionMcpServer(options) {
24
24
  const tools = createBackgroundSessionToolContracts().filter(({ id }) => enabledTools.has(id.slice(11))).map((contract) => ({
25
25
  description: contract.description,
26
26
  inputSchema: contract.inputSchema,
27
- name: contract.id,
28
- outputSchema: { type: "object" }
27
+ name: contract.id
29
28
  }));
30
29
  const serverInfo = {
31
30
  name: options.serverName ?? "rivus-background-sessions",
32
31
  version: options.serverVersion ?? "1.0.0"
33
32
  };
34
33
  return { run: () => runMcpServer({
35
- controlContext: options.controlContext,
34
+ capability: options.capability,
36
35
  controlToken: options.controlToken,
37
36
  controlUrl: options.controlUrl,
38
37
  fetchImplementation,
@@ -45,10 +44,10 @@ function createBackgroundSessionMcpServer(options) {
45
44
  async function runBackgroundSessionMcpServer(options = {}) {
46
45
  const controlUrl = requireEnv("RIVUS_MCP_CONTROL_URL");
47
46
  const controlToken = requireEnv("RIVUS_MCP_CONTROL_TOKEN");
48
- const context = parseContext(requireEnv("RIVUS_MCP_CONTEXT"));
47
+ const capability = requireEnv("RIVUS_MCP_CAPABILITY");
49
48
  const enabledTools = optionalEnv("RIVUS_MCP_TOOLS")?.split(",").map((tool) => tool.trim()).filter(Boolean);
50
49
  await createBackgroundSessionMcpServer({
51
- controlContext: context,
50
+ capability,
52
51
  controlToken,
53
52
  controlUrl,
54
53
  ...enabledTools ? { enabledTools } : {},
@@ -60,16 +59,17 @@ async function runBackgroundSessionMcpServer(options = {}) {
60
59
  async function runMcpServer(options) {
61
60
  const send = (message) => {
62
61
  const body = JSON.stringify(message);
63
- options.output.write(`Content-Length: ${Buffer.byteLength(body)}\r\n\r\n${body}`);
62
+ options.output.write(`${body}\n`);
64
63
  };
65
64
  for await (const message of readMcpMessages(options.input)) {
66
- if (!isRecord(message) || typeof message.id !== "number") continue;
65
+ if (!isRecord(message) || !isJsonRpcRequestId(message.id)) continue;
67
66
  const method = typeof message.method === "string" ? message.method : void 0;
68
67
  if (!method) continue;
69
68
  try {
70
69
  switch (method) {
71
70
  case "initialize":
72
71
  send({
72
+ jsonrpc: "2.0",
73
73
  id: message.id,
74
74
  result: {
75
75
  capabilities: { tools: { listChanged: false } },
@@ -80,12 +80,14 @@ async function runMcpServer(options) {
80
80
  break;
81
81
  case "ping":
82
82
  send({
83
+ jsonrpc: "2.0",
83
84
  id: message.id,
84
85
  result: {}
85
86
  });
86
87
  break;
87
88
  case "tools/list":
88
89
  send({
90
+ jsonrpc: "2.0",
89
91
  id: message.id,
90
92
  result: { tools: options.tools }
91
93
  });
@@ -96,6 +98,7 @@ async function runMcpServer(options) {
96
98
  const command = name.startsWith("background.") ? name.slice(11) : void 0;
97
99
  if (!command || !options.tools.some((tool) => tool.name === name)) {
98
100
  send({
101
+ jsonrpc: "2.0",
99
102
  id: message.id,
100
103
  error: {
101
104
  code: -32602,
@@ -106,8 +109,8 @@ async function runMcpServer(options) {
106
109
  }
107
110
  const response = await options.fetchImplementation(options.controlUrl, {
108
111
  body: JSON.stringify({
112
+ capability: options.capability,
109
113
  command,
110
- context: options.controlContext,
111
114
  input: params.arguments ?? {}
112
115
  }),
113
116
  headers: {
@@ -120,6 +123,7 @@ async function runMcpServer(options) {
120
123
  if (!response.ok || envelope.error) {
121
124
  const messageText = envelope.error?.message ?? `background session control failed with ${response.status}`;
122
125
  send({
126
+ jsonrpc: "2.0",
123
127
  id: message.id,
124
128
  error: {
125
129
  code: -32e3,
@@ -129,6 +133,7 @@ async function runMcpServer(options) {
129
133
  break;
130
134
  }
131
135
  send({
136
+ jsonrpc: "2.0",
132
137
  id: message.id,
133
138
  result: {
134
139
  content: [{
@@ -141,6 +146,7 @@ async function runMcpServer(options) {
141
146
  break;
142
147
  }
143
148
  default: send({
149
+ jsonrpc: "2.0",
144
150
  id: message.id,
145
151
  error: {
146
152
  code: -32601,
@@ -150,6 +156,7 @@ async function runMcpServer(options) {
150
156
  }
151
157
  } catch (error) {
152
158
  send({
159
+ jsonrpc: "2.0",
153
160
  id: message.id,
154
161
  error: {
155
162
  code: -32e3,
@@ -163,36 +170,25 @@ async function* readMcpMessages(input) {
163
170
  let buffer = "";
164
171
  for await (const chunk of input) {
165
172
  buffer += chunk.toString("utf8");
166
- let parsed = parseNextMessage(buffer);
167
- while (parsed) {
168
- yield parsed.message;
169
- buffer = Buffer.from(buffer).subarray(parsed.byteLength).toString("utf8");
170
- parsed = parseNextMessage(buffer);
173
+ let newlineIndex = buffer.indexOf("\n");
174
+ while (newlineIndex >= 0) {
175
+ const line = buffer.slice(0, newlineIndex).replace(/\r$/, "");
176
+ buffer = buffer.slice(newlineIndex + 1);
177
+ if (line.trim() !== "") yield parseMcpMessage(line);
178
+ newlineIndex = buffer.indexOf("\n");
171
179
  }
172
180
  }
181
+ if (buffer.trim() !== "") yield parseMcpMessage(buffer.trim());
173
182
  }
174
- function parseNextMessage(buffer) {
175
- const headerEnd = buffer.indexOf("\r\n\r\n");
176
- if (headerEnd < 0) return void 0;
177
- const contentLength = buffer.slice(0, headerEnd).split("\r\n").find((header) => /^Content-Length:\s*\d+$/i.test(header));
178
- if (!contentLength) throw new BackgroundSessionMcpError("MCP message is missing a Content-Length header");
179
- const byteLength = Number(contentLength.slice(15).trim());
180
- const headerBytes = Buffer.byteLength(buffer.slice(0, headerEnd + 4));
181
- if (Buffer.byteLength(buffer) < headerBytes + byteLength) return void 0;
182
- const payload = Buffer.from(buffer).subarray(headerBytes, headerBytes + byteLength).toString("utf8");
183
+ function parseMcpMessage(payload) {
183
184
  try {
184
- return {
185
- byteLength: headerBytes + byteLength,
186
- message: JSON.parse(payload)
187
- };
185
+ return JSON.parse(payload);
188
186
  } catch {
189
187
  throw new BackgroundSessionMcpError("invalid MCP JSON payload");
190
188
  }
191
189
  }
192
- function parseContext(value) {
193
- const parsed = JSON.parse(value);
194
- if (!isRecord(parsed)) throw new BackgroundSessionMcpError("RIVUS_MCP_CONTEXT must be a JSON object");
195
- return parsed;
190
+ function isJsonRpcRequestId(value) {
191
+ return value === null || typeof value === "string" || typeof value === "number";
196
192
  }
197
193
  function requireEnv(name) {
198
194
  const value = process.env[name]?.trim();
@@ -335,6 +331,8 @@ const COMMANDS = [
335
331
  function createBackgroundSessionControlHttpServer(options) {
336
332
  let server;
337
333
  let boundPort = options.port;
334
+ const capabilities = /* @__PURE__ */ new Map();
335
+ const capabilitiesBySessionKey = /* @__PURE__ */ new Map();
338
336
  const serverHandle = createServer((request, response) => {
339
337
  (async () => {
340
338
  try {
@@ -349,11 +347,13 @@ function createBackgroundSessionControlHttpServer(options) {
349
347
  if (request.method !== "POST" || request.url !== "/background-sessions") throw new BackgroundSessionControlHttpError(404, "not found");
350
348
  if (request.headers.authorization !== `Bearer ${options.token}`) throw new BackgroundSessionControlHttpError(401, "unauthorized");
351
349
  const envelope = parseEnvelope(await readBody(request, 64 * 1024));
350
+ const context = capabilities.get(envelope.capability);
351
+ if (!context) throw new BackgroundSessionControlHttpError(401, "unknown or expired capability");
352
352
  const command = envelope.command;
353
353
  if (!COMMANDS.includes(command)) throw new BackgroundSessionControlHttpError(400, `unsupported background session command: ${String(command)}`);
354
354
  respond(response, 200, {
355
355
  ok: true,
356
- result: await options.control.handle(command, envelope.context, envelope.input)
356
+ result: await options.control.handle(command, context, envelope.input)
357
357
  });
358
358
  } catch (error) {
359
359
  respond(response, error instanceof BackgroundSessionControlHttpError ? error.statusCode : 500, {
@@ -372,8 +372,18 @@ function createBackgroundSessionControlHttpServer(options) {
372
372
  close: async () => {
373
373
  const active = server;
374
374
  server = void 0;
375
+ capabilities.clear();
376
+ capabilitiesBySessionKey.clear();
375
377
  if (active) await new Promise((resolve) => active.close(() => resolve()));
376
378
  },
379
+ issueCapability: (context) => {
380
+ const existing = capabilitiesBySessionKey.get(context.sessionKey);
381
+ if (existing) return existing;
382
+ const capability = randomUUID();
383
+ capabilities.set(capability, context);
384
+ capabilitiesBySessionKey.set(context.sessionKey, capability);
385
+ return capability;
386
+ },
377
387
  start: async () => {
378
388
  if (server) return;
379
389
  await new Promise((resolve, reject) => {
@@ -399,10 +409,10 @@ function parseEnvelope(body) {
399
409
  const parsed = JSON.parse(body);
400
410
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new BackgroundSessionControlHttpError(400, "invalid background session control envelope");
401
411
  const record = parsed;
402
- if (typeof record.command !== "string" || record.context === void 0) throw new BackgroundSessionControlHttpError(400, "background session control envelope requires command and context");
412
+ if (typeof record.command !== "string" || typeof record.capability !== "string") throw new BackgroundSessionControlHttpError(400, "background session control envelope requires command and capability");
403
413
  return {
414
+ capability: record.capability,
404
415
  command: record.command,
405
- context: record.context,
406
416
  input: record.input
407
417
  };
408
418
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rivus/agent",
3
- "version": "0.8.6",
3
+ "version": "0.9.0",
4
4
  "description": "A local agent daemon core built around a usable agent harness and domain events.",
5
5
  "type": "module",
6
6
  "license": "MIT",