@rivus/agent 0.1.0 → 0.3.1

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.
@@ -6,6 +6,489 @@ import { Effect } from "effect";
6
6
  import { open, readFile, realpath, stat } from "node:fs/promises";
7
7
  import { createHash, randomUUID } from "node:crypto";
8
8
  import { constants } from "node:fs";
9
+ //#region src/application/plugin/rivus-automation-runtime-definition.ts
10
+ function resolveRivusAutomationRuntimeDefinition(definition, requestedToolIds, requestedSkillIds) {
11
+ const toolIds = Object.freeze([...new Set(requestedToolIds)].sort());
12
+ const toolsById = new Map(definition.tools.map((tool) => [tool.id, tool]));
13
+ const tools = Object.freeze(toolIds.map((toolId) => {
14
+ const tool = toolsById.get(toolId);
15
+ if (!tool) throw new Error(`Automation Runtime requests ungranted tool: ${toolId}`);
16
+ return tool;
17
+ }));
18
+ const skillIds = Object.freeze([...new Set(requestedSkillIds)].sort());
19
+ const skillsById = new Map(definition.skills.map((skill) => [skill.id, skill]));
20
+ const skills = Object.freeze(skillIds.map((skillId) => {
21
+ const skill = skillsById.get(skillId);
22
+ if (!skill) throw new Error(`Automation Runtime requests ungranted skill: ${skillId}`);
23
+ return skill;
24
+ }));
25
+ return deepFreeze({
26
+ ...definition,
27
+ memory: {
28
+ scopes: [],
29
+ tool: false
30
+ },
31
+ skillGrantSet: {
32
+ revision: createHash("sha256").update(JSON.stringify({
33
+ parentRevision: definition.skillGrantSet.revision,
34
+ skillIds
35
+ })).digest("hex"),
36
+ skillIds
37
+ },
38
+ skills,
39
+ toolGrantSet: {
40
+ revision: createHash("sha256").update(JSON.stringify({
41
+ parentRevision: definition.toolGrantSet.revision,
42
+ toolIds
43
+ })).digest("hex"),
44
+ toolIds
45
+ },
46
+ tools
47
+ });
48
+ }
49
+ //#endregion
50
+ //#region src/application/plugin/rivus-plugin-loader.ts
51
+ var RivusPluginLoadError = class extends Error {
52
+ pluginId;
53
+ moduleSpecifier;
54
+ name = "RivusPluginLoadError";
55
+ constructor(pluginId, moduleSpecifier, message, options) {
56
+ super(message, options);
57
+ this.pluginId = pluginId;
58
+ this.moduleSpecifier = moduleSpecifier;
59
+ }
60
+ };
61
+ async function loadRivusDeployment(options) {
62
+ validateRivusDeploymentManifest(options.manifest);
63
+ const catalog = createRivusPluginCatalog();
64
+ const pluginStatuses = [];
65
+ const statusByPlugin = /* @__PURE__ */ new Map();
66
+ for (const declaration of options.manifest.plugins) try {
67
+ const plugin = await resolvePluginExport(await options.loadModule({
68
+ deploymentRoot: options.deploymentRoot,
69
+ module: declaration.module,
70
+ pluginId: declaration.id
71
+ }));
72
+ if (plugin.manifest.id !== declaration.id) throw new Error(`plugin manifest id ${plugin.manifest.id} does not match declaration ${declaration.id}`);
73
+ catalog.registerPlugin(plugin);
74
+ const status = Object.freeze({
75
+ id: declaration.id,
76
+ module: declaration.module,
77
+ required: declaration.required,
78
+ status: "loaded",
79
+ version: plugin.manifest.version
80
+ });
81
+ pluginStatuses.push(status);
82
+ statusByPlugin.set(declaration.id, status);
83
+ } catch (cause) {
84
+ const message = cause instanceof Error ? cause.message : String(cause);
85
+ if (declaration.required) throw new RivusPluginLoadError(declaration.id, declaration.module, `required plugin ${declaration.id} failed to load: ${message}`, { cause });
86
+ const status = Object.freeze({
87
+ error: message,
88
+ id: declaration.id,
89
+ module: declaration.module,
90
+ required: false,
91
+ status: "failed"
92
+ });
93
+ pluginStatuses.push(status);
94
+ statusByPlugin.set(declaration.id, status);
95
+ }
96
+ const agentStatuses = [];
97
+ const definitions = [];
98
+ for (const agent of options.manifest.agents) {
99
+ const pluginStatus = statusByPlugin.get(agent.pluginId);
100
+ if (pluginStatus.status === "failed") {
101
+ agentStatuses.push(Object.freeze({
102
+ agentId: agent.agentId,
103
+ pluginId: agent.pluginId,
104
+ profileId: agent.profileId,
105
+ reason: `plugin ${agent.pluginId} is unavailable: ${pluginStatus.error}`,
106
+ status: "disabled"
107
+ }));
108
+ continue;
109
+ }
110
+ try {
111
+ const definition = resolveRivusAgentDefinition(catalog, agent);
112
+ definitions.push(definition);
113
+ agentStatuses.push(Object.freeze({
114
+ agentId: agent.agentId,
115
+ definition,
116
+ pluginId: agent.pluginId,
117
+ profileId: agent.profileId,
118
+ status: "enabled"
119
+ }));
120
+ } catch (cause) {
121
+ const declaration = options.manifest.plugins.find(({ id }) => id === agent.pluginId);
122
+ const message = cause instanceof Error ? cause.message : String(cause);
123
+ if (declaration.required) throw new RivusPluginLoadError(declaration.id, declaration.module, `deployment ${agent.agentId} failed to resolve: ${message}`, { cause });
124
+ agentStatuses.push(Object.freeze({
125
+ agentId: agent.agentId,
126
+ pluginId: agent.pluginId,
127
+ profileId: agent.profileId,
128
+ reason: message,
129
+ status: "disabled"
130
+ }));
131
+ }
132
+ }
133
+ const agentStatusById = new Map(agentStatuses.map((agent) => [agent.agentId, agent]));
134
+ const automationTemplates = new Map(catalog.snapshot().automations.map((template) => [template.id, template]));
135
+ const automationDefinitions = [];
136
+ for (const automation of options.manifest.automations ?? []) {
137
+ const agent = agentStatusById.get(automation.agentId);
138
+ if (!agent || agent.status === "disabled" || !agent.definition) continue;
139
+ const template = automationTemplates.get(automation.templateId);
140
+ if (!template) throw new Error(`automation ${automation.id} references unknown template: ${automation.templateId}`);
141
+ if (template.pluginId !== agent.pluginId || template.profileId !== agent.profileId) throw new Error(`automation ${automation.id} template is not owned by agent profile ${agent.profileId}`);
142
+ automationDefinitions.push(deepFreeze({
143
+ ...automation,
144
+ runtimeDefinition: resolveRivusAutomationRuntimeDefinition(agent.definition, template.requestedToolIds, template.requestedSkillIds),
145
+ template
146
+ }));
147
+ }
148
+ return Object.freeze({
149
+ agents: Object.freeze(agentStatuses),
150
+ automationDefinitions: Object.freeze(automationDefinitions),
151
+ catalog,
152
+ definitions: Object.freeze(definitions),
153
+ manifest: options.manifest,
154
+ plugins: Object.freeze(pluginStatuses)
155
+ });
156
+ }
157
+ function validateRivusDeploymentManifest(manifest) {
158
+ const pluginIds = /* @__PURE__ */ new Set();
159
+ for (const plugin of manifest.plugins) {
160
+ if (pluginIds.has(plugin.id)) throw new Error(`duplicate plugin declaration: ${plugin.id}`);
161
+ validateModuleSpecifier(plugin.module);
162
+ pluginIds.add(plugin.id);
163
+ }
164
+ const agentIds = /* @__PURE__ */ new Set();
165
+ const agentById = /* @__PURE__ */ new Map();
166
+ for (const agent of manifest.agents) {
167
+ if (agentIds.has(agent.agentId)) throw new Error(`duplicate agent deployment: ${agent.agentId}`);
168
+ agentIds.add(agent.agentId);
169
+ if (!pluginIds.has(agent.pluginId)) throw new Error(`agent ${agent.agentId} references undeclared plugin: ${agent.pluginId}`);
170
+ agentById.set(agent.agentId, agent);
171
+ }
172
+ const automationIds = /* @__PURE__ */ new Set();
173
+ for (const automation of manifest.automations ?? []) {
174
+ if (automationIds.has(automation.id)) throw new Error(`duplicate automation binding: ${automation.id}`);
175
+ automationIds.add(automation.id);
176
+ const agent = agentById.get(automation.agentId);
177
+ if (!agent) throw new Error(`automation ${automation.id} references unknown agent: ${automation.agentId}`);
178
+ const endpoint = manifest.endpoints.find(({ id }) => id === automation.delivery.endpointId);
179
+ if (!endpoint) throw new Error(`automation ${automation.id} references unknown delivery endpoint: ${automation.delivery.endpointId}`);
180
+ if (endpoint.agentId !== agent.agentId) throw new Error(`automation ${automation.id} delivery endpoint is bound to a different agent`);
181
+ }
182
+ const endpointIds = /* @__PURE__ */ new Set();
183
+ const sessionNamespaces = /* @__PURE__ */ new Set();
184
+ for (const endpoint of manifest.endpoints) {
185
+ if (endpointIds.has(endpoint.id)) throw new Error(`duplicate endpoint binding: ${endpoint.id}`);
186
+ endpointIds.add(endpoint.id);
187
+ if (sessionNamespaces.has(endpoint.sessionNamespace)) throw new Error(`duplicate endpoint session namespace: ${endpoint.sessionNamespace}`);
188
+ sessionNamespaces.add(endpoint.sessionNamespace);
189
+ const agent = agentById.get(endpoint.agentId);
190
+ if (!agent) throw new Error(`endpoint ${endpoint.id} references unknown agent: ${endpoint.agentId}`);
191
+ if (!agent.endpointIds.includes(endpoint.id)) throw new Error(`endpoint ${endpoint.id} is not declared by agent ${endpoint.agentId}`);
192
+ }
193
+ for (const agent of manifest.agents) for (const endpointId of agent.endpointIds) {
194
+ const endpoint = manifest.endpoints.find(({ id }) => id === endpointId);
195
+ if (!endpoint) throw new Error(`agent ${agent.agentId} references unknown endpoint: ${endpointId}`);
196
+ if (endpoint.agentId !== agent.agentId) throw new Error(`endpoint ${endpointId} is bound to a different agent`);
197
+ }
198
+ const defaultAgent = agentById.get(manifest.defaultAgentId);
199
+ if (!defaultAgent) throw new Error(`default agent does not exist: ${manifest.defaultAgentId}`);
200
+ const defaultEndpoint = manifest.endpoints.find(({ id }) => id === manifest.defaultEndpointId);
201
+ if (!defaultEndpoint) throw new Error(`default endpoint does not exist: ${manifest.defaultEndpointId}`);
202
+ if (defaultEndpoint.agentId !== defaultAgent.agentId) throw new Error("default endpoint is not bound to the default agent");
203
+ if (!defaultEndpoint.enabled) throw new Error("default endpoint must be enabled");
204
+ }
205
+ function validateModuleSpecifier(moduleSpecifier) {
206
+ if (moduleSpecifier.trim() === "" || isAbsolute(moduleSpecifier) || /^[a-z][a-z+.-]*:/i.test(moduleSpecifier) || moduleSpecifier.includes("\0")) throw new Error(`invalid plugin module specifier: ${moduleSpecifier}`);
207
+ if ((moduleSpecifier.startsWith("./") || moduleSpecifier.startsWith("../")) && moduleSpecifier.split(/[\\/]/).includes("..")) throw new Error(`plugin module escapes deployment root: ${moduleSpecifier}`);
208
+ }
209
+ async function resolvePluginExport(module) {
210
+ const candidate = "default" in module ? module.default : module;
211
+ const plugin = typeof candidate === "function" ? await candidate() : candidate;
212
+ if (plugin === null || typeof plugin !== "object" || !("manifest" in plugin) || !("register" in plugin) || typeof plugin.register !== "function") throw new Error("plugin module default export is not a RivusPlugin or factory");
213
+ return plugin;
214
+ }
215
+ //#endregion
216
+ //#region src/infrastructure/config/local-env-file.ts
217
+ var LocalEnvFileError = class extends Error {
218
+ constructor(message) {
219
+ super(message);
220
+ this.name = "LocalEnvFileError";
221
+ }
222
+ };
223
+ async function loadMergedLocalEnvFile(filePath, overrideEnv) {
224
+ return mergeRivusDaemonEnv(await loadLocalEnvFile(filePath), overrideEnv);
225
+ }
226
+ async function loadLocalEnvFile(filePath) {
227
+ return parseLocalEnvFile(await readFile(filePath, "utf8"));
228
+ }
229
+ function parseLocalEnvFile(contents) {
230
+ const env = {};
231
+ const lines = contents.split(/\r?\n/);
232
+ for (let index = 0; index < lines.length; index += 1) {
233
+ const trimmed = lines[index].trim();
234
+ if (!trimmed || trimmed.startsWith("#")) continue;
235
+ const match = (trimmed.startsWith("export ") ? trimmed.slice(7).trimStart() : trimmed).match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
236
+ if (!match) throw new LocalEnvFileError(`Invalid env file line ${index + 1}`);
237
+ const key = match[1];
238
+ const rawValue = match[2];
239
+ env[key] = parseEnvValue(rawValue, index + 1);
240
+ }
241
+ return env;
242
+ }
243
+ function mergeRivusDaemonEnv(fileEnv, overrideEnv) {
244
+ const merged = { ...fileEnv };
245
+ for (const [key, value] of Object.entries(overrideEnv)) if (value !== void 0) merged[key] = value;
246
+ return merged;
247
+ }
248
+ function parseEnvValue(rawValue, lineNumber) {
249
+ const value = rawValue.trim();
250
+ if (!value) return "";
251
+ if (value.startsWith("'")) {
252
+ if (!value.endsWith("'")) throw new LocalEnvFileError(`Invalid single-quoted env value on line ${lineNumber}`);
253
+ return value.slice(1, -1).replaceAll("'\\''", "'");
254
+ }
255
+ if (value.startsWith("\"")) {
256
+ if (!value.endsWith("\"")) throw new LocalEnvFileError(`Invalid double-quoted env value on line ${lineNumber}`);
257
+ return unescapeDoubleQuotedValue(value.slice(1, -1));
258
+ }
259
+ return value;
260
+ }
261
+ function unescapeDoubleQuotedValue(value) {
262
+ return value.replace(/\\(["\\nrt])/g, (_match, escaped) => {
263
+ switch (escaped) {
264
+ case "n": return "\n";
265
+ case "r": return "\r";
266
+ case "t": return " ";
267
+ default: return escaped;
268
+ }
269
+ });
270
+ }
271
+ //#endregion
272
+ //#region src/infrastructure/config/feishu-endpoint-credentials.ts
273
+ var FeishuEndpointCredentialError = class extends Error {
274
+ name = "FeishuEndpointCredentialError";
275
+ };
276
+ function resolveFeishuEndpointCredentials(credentialRef, env) {
277
+ if (!credentialRef.startsWith("env:")) throw new FeishuEndpointCredentialError("Feishu endpoint credentialRef must use env:<PREFIX>");
278
+ const prefix = credentialRef.slice(4);
279
+ if (!/^[A-Z][A-Z0-9_]*$/.test(prefix)) throw new FeishuEndpointCredentialError(`Invalid environment prefix in credentialRef: ${credentialRef}`);
280
+ return Object.freeze({
281
+ appId: required$1(env, `${prefix}_APP_ID`),
282
+ appSecret: required$1(env, `${prefix}_APP_SECRET`)
283
+ });
284
+ }
285
+ function required$1(env, variable) {
286
+ const value = env[variable]?.trim();
287
+ if (!value) throw new FeishuEndpointCredentialError(`${variable} is required`);
288
+ return value;
289
+ }
290
+ //#endregion
291
+ //#region src/infrastructure/config/rivus-deployment-manifest.ts
292
+ var RivusDeploymentManifestError = class extends Error {
293
+ manifestPath;
294
+ name = "RivusDeploymentManifestError";
295
+ constructor(manifestPath, message, options) {
296
+ super(message, options);
297
+ this.manifestPath = manifestPath;
298
+ }
299
+ };
300
+ async function loadRivusDeploymentManifest(manifestPath, options = {}) {
301
+ const maxBytes = options.maxBytes ?? 1024 * 1024;
302
+ try {
303
+ const metadata = await stat(manifestPath);
304
+ if (!metadata.isFile()) throw new Error("deployment manifest must be a regular file");
305
+ if (metadata.size > maxBytes) throw new Error(`deployment manifest exceeds ${maxBytes} byte limit`);
306
+ const bytes = await readFile(manifestPath);
307
+ const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
308
+ return parseManifest(JSON.parse(text));
309
+ } catch (cause) {
310
+ if (cause instanceof RivusDeploymentManifestError) throw cause;
311
+ throw new RivusDeploymentManifestError(manifestPath, `failed to load Rivus deployment manifest: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
312
+ }
313
+ }
314
+ function parseManifest(value) {
315
+ const root = record(value, "manifest");
316
+ exactKeys(root, [
317
+ "agents",
318
+ "automations",
319
+ "defaultAgentId",
320
+ "defaultEndpointId",
321
+ "endpoints",
322
+ "plugins"
323
+ ], "manifest", ["automations"]);
324
+ const plugins = array(root.plugins, "manifest.plugins").map((entry, index) => {
325
+ const plugin = record(entry, `manifest.plugins[${index}]`);
326
+ exactKeys(plugin, [
327
+ "id",
328
+ "module",
329
+ "required"
330
+ ], `manifest.plugins[${index}]`);
331
+ return Object.freeze({
332
+ id: string(plugin.id, `manifest.plugins[${index}].id`),
333
+ module: string(plugin.module, `manifest.plugins[${index}].module`),
334
+ required: boolean(plugin.required, `manifest.plugins[${index}].required`)
335
+ });
336
+ });
337
+ const agents = array(root.agents, "manifest.agents").map((entry, index) => {
338
+ const agent = record(entry, `manifest.agents[${index}]`);
339
+ exactKeys(agent, [
340
+ "agentId",
341
+ "endpointIds",
342
+ "memory",
343
+ "pluginId",
344
+ "profileId",
345
+ "skills",
346
+ "tools"
347
+ ], `manifest.agents[${index}]`, ["memory"]);
348
+ const memory = agent.memory === void 0 ? void 0 : record(agent.memory, `manifest.agents[${index}].memory`);
349
+ if (memory) exactKeys(memory, ["scopes", "tool"], `manifest.agents[${index}].memory`);
350
+ const skills = record(agent.skills, `manifest.agents[${index}].skills`);
351
+ exactKeys(skills, ["allow"], `manifest.agents[${index}].skills`);
352
+ const tools = record(agent.tools, `manifest.agents[${index}].tools`);
353
+ exactKeys(tools, ["allow"], `manifest.agents[${index}].tools`);
354
+ return Object.freeze({
355
+ agentId: string(agent.agentId, `manifest.agents[${index}].agentId`),
356
+ endpointIds: Object.freeze(array(agent.endpointIds, `manifest.agents[${index}].endpointIds`).map((item, itemIndex) => string(item, `manifest.agents[${index}].endpointIds[${itemIndex}]`))),
357
+ ...memory ? { memory: Object.freeze({
358
+ scopes: Object.freeze(array(memory.scopes, `manifest.agents[${index}].memory.scopes`).map((item, itemIndex) => memoryScope(item, `manifest.agents[${index}].memory.scopes[${itemIndex}]`))),
359
+ tool: boolean(memory.tool, `manifest.agents[${index}].memory.tool`)
360
+ }) } : {},
361
+ pluginId: string(agent.pluginId, `manifest.agents[${index}].pluginId`),
362
+ profileId: string(agent.profileId, `manifest.agents[${index}].profileId`),
363
+ skills: Object.freeze({ allow: Object.freeze(array(skills.allow, `manifest.agents[${index}].skills.allow`).map((item, itemIndex) => string(item, `manifest.agents[${index}].skills.allow[${itemIndex}]`))) }),
364
+ tools: Object.freeze({ allow: Object.freeze(array(tools.allow, `manifest.agents[${index}].tools.allow`).map((item, itemIndex) => string(item, `manifest.agents[${index}].tools.allow[${itemIndex}]`))) })
365
+ });
366
+ });
367
+ const endpoints = array(root.endpoints, "manifest.endpoints").map((entry, index) => {
368
+ const endpoint = record(entry, `manifest.endpoints[${index}]`);
369
+ exactKeys(endpoint, [
370
+ "agentId",
371
+ "baseUrl",
372
+ "credentialRef",
373
+ "enabled",
374
+ "experimental",
375
+ "groupPolicy",
376
+ "id",
377
+ "required",
378
+ "sessionNamespace",
379
+ "streamMinIntervalMs"
380
+ ], `manifest.endpoints[${index}]`, ["experimental"]);
381
+ const experimental = endpoint.experimental === void 0 ? void 0 : record(endpoint.experimental, `manifest.endpoints[${index}].experimental`);
382
+ if (experimental) exactKeys(experimental, ["cotMessages"], `manifest.endpoints[${index}].experimental`);
383
+ return Object.freeze({
384
+ agentId: string(endpoint.agentId, `manifest.endpoints[${index}].agentId`),
385
+ baseUrl: string(endpoint.baseUrl, `manifest.endpoints[${index}].baseUrl`),
386
+ credentialRef: string(endpoint.credentialRef, `manifest.endpoints[${index}].credentialRef`),
387
+ enabled: boolean(endpoint.enabled, `manifest.endpoints[${index}].enabled`),
388
+ ...experimental ? { experimental: Object.freeze({ cotMessages: boolean(experimental.cotMessages, `manifest.endpoints[${index}].experimental.cotMessages`) }) } : {},
389
+ groupPolicy: groupPolicy(endpoint.groupPolicy, `manifest.endpoints[${index}].groupPolicy`),
390
+ id: string(endpoint.id, `manifest.endpoints[${index}].id`),
391
+ required: boolean(endpoint.required, `manifest.endpoints[${index}].required`),
392
+ sessionNamespace: string(endpoint.sessionNamespace, `manifest.endpoints[${index}].sessionNamespace`),
393
+ streamMinIntervalMs: positiveInteger(endpoint.streamMinIntervalMs, `manifest.endpoints[${index}].streamMinIntervalMs`)
394
+ });
395
+ });
396
+ const automations = array(root.automations ?? [], "manifest.automations").map((entry, index) => {
397
+ const automation = record(entry, `manifest.automations[${index}]`);
398
+ exactKeys(automation, [
399
+ "agentId",
400
+ "delivery",
401
+ "enabled",
402
+ "id",
403
+ "required",
404
+ "schedule",
405
+ "templateId",
406
+ "timeZone"
407
+ ], `manifest.automations[${index}]`);
408
+ const delivery = record(automation.delivery, `manifest.automations[${index}].delivery`);
409
+ exactKeys(delivery, [
410
+ "endpointId",
411
+ "targetRef",
412
+ "targetType"
413
+ ], `manifest.automations[${index}].delivery`);
414
+ return Object.freeze({
415
+ agentId: string(automation.agentId, `manifest.automations[${index}].agentId`),
416
+ delivery: Object.freeze({
417
+ endpointId: string(delivery.endpointId, `manifest.automations[${index}].delivery.endpointId`),
418
+ targetRef: string(delivery.targetRef, `manifest.automations[${index}].delivery.targetRef`),
419
+ targetType: automationTargetType(delivery.targetType, `manifest.automations[${index}].delivery.targetType`)
420
+ }),
421
+ enabled: boolean(automation.enabled, `manifest.automations[${index}].enabled`),
422
+ id: string(automation.id, `manifest.automations[${index}].id`),
423
+ required: boolean(automation.required, `manifest.automations[${index}].required`),
424
+ schedule: string(automation.schedule, `manifest.automations[${index}].schedule`),
425
+ templateId: string(automation.templateId, `manifest.automations[${index}].templateId`),
426
+ timeZone: string(automation.timeZone, `manifest.automations[${index}].timeZone`)
427
+ });
428
+ });
429
+ return Object.freeze({
430
+ agents: Object.freeze(agents),
431
+ automations: Object.freeze(automations),
432
+ defaultAgentId: string(root.defaultAgentId, "manifest.defaultAgentId"),
433
+ defaultEndpointId: string(root.defaultEndpointId, "manifest.defaultEndpointId"),
434
+ endpoints: Object.freeze(endpoints),
435
+ plugins: Object.freeze(plugins)
436
+ });
437
+ }
438
+ function record(value, path) {
439
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error(`${path} must be an object`);
440
+ return value;
441
+ }
442
+ function array(value, path) {
443
+ if (!Array.isArray(value)) throw new Error(`${path} must be an array`);
444
+ return value;
445
+ }
446
+ function string(value, path) {
447
+ if (typeof value !== "string" || value.trim() === "") throw new Error(`${path} must be a non-empty string`);
448
+ return value;
449
+ }
450
+ function boolean(value, path) {
451
+ if (typeof value !== "boolean") throw new Error(`${path} must be a boolean`);
452
+ return value;
453
+ }
454
+ function positiveInteger(value, path) {
455
+ if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`${path} must be a positive integer`);
456
+ return value;
457
+ }
458
+ function memoryScope(value, path) {
459
+ if (typeof value !== "string" || !MEMORY_SCOPES.includes(value)) throw new Error(`${path} must be conversation, agent-private, or shared-user-profile`);
460
+ return value;
461
+ }
462
+ function groupPolicy(value, path) {
463
+ if (value !== "mention-only" && value !== "ignore-unmentioned" && value !== "default-responder") throw new Error(`${path} must be mention-only, ignore-unmentioned, or default-responder`);
464
+ return value;
465
+ }
466
+ function automationTargetType(value, path) {
467
+ if (value !== "chat_id" && value !== "open_id" && value !== "user_id" && value !== "union_id" && value !== "email") throw new Error(`${path} must be chat_id, open_id, user_id, union_id, or email`);
468
+ return value;
469
+ }
470
+ function exactKeys(value, allowed, path, optional = []) {
471
+ const unexpected = Object.keys(value).find((key) => !allowed.includes(key));
472
+ if (unexpected) throw new Error(`${path} contains unsupported field: ${unexpected}`);
473
+ const missing = allowed.find((key) => !optional.includes(key) && !Object.hasOwn(value, key));
474
+ if (missing) throw new Error(`${path} is missing required field: ${missing}`);
475
+ }
476
+ //#endregion
477
+ //#region src/infrastructure/plugin/node-rivus-plugin-module-loader.ts
478
+ async function loadNodeRivusPluginModule(request) {
479
+ return await import(pathToFileURL(await resolveNodeRivusPluginModulePath(request)).href);
480
+ }
481
+ async function resolveNodeRivusPluginModulePath(request) {
482
+ const deploymentRoot = await realpath(request.deploymentRoot);
483
+ const resolvedRealpath = await realpath(createRequire(join(deploymentRoot, "package.json")).resolve(request.module));
484
+ if (!isWithin(deploymentRoot, resolvedRealpath)) throw new Error(`plugin module ${request.module} resolves outside deployment root: ${resolvedRealpath}`);
485
+ return resolvedRealpath;
486
+ }
487
+ function isWithin(root, candidate) {
488
+ const child = relative(root, candidate);
489
+ return child === "" || !child.startsWith(`..${sep}`) && child !== ".." && !isAbsolute(child);
490
+ }
491
+ //#endregion
9
492
  //#region src/infrastructure/config/rivus-daemon-config.ts
10
493
  var RivusDaemonConfigError = class {
11
494
  variable;
@@ -86,69 +569,13 @@ function optionalPositiveInteger(value, variable, fallback) {
86
569
  function required(env, variable) {
87
570
  const value = optional(env[variable]);
88
571
  if (value) return Effect.succeed(value);
89
- return Effect.fail(new RivusDaemonConfigError(variable, `${variable} is required`));
90
- }
91
- function optionalThinkingLevel(value) {
92
- const normalized = optional(value);
93
- if (!normalized) return Effect.succeed(void 0);
94
- if (THINKING_LEVELS$1.has(normalized)) return Effect.succeed(normalized);
95
- return Effect.fail(new RivusDaemonConfigError("PI_THINKING_LEVEL", "PI_THINKING_LEVEL must be one of off, minimal, low, medium, high, xhigh"));
96
- }
97
- //#endregion
98
- //#region src/infrastructure/config/local-env-file.ts
99
- var LocalEnvFileError = class extends Error {
100
- constructor(message) {
101
- super(message);
102
- this.name = "LocalEnvFileError";
103
- }
104
- };
105
- async function loadMergedLocalEnvFile(filePath, overrideEnv) {
106
- return mergeRivusDaemonEnv(await loadLocalEnvFile(filePath), overrideEnv);
107
- }
108
- async function loadLocalEnvFile(filePath) {
109
- return parseLocalEnvFile(await readFile(filePath, "utf8"));
110
- }
111
- function parseLocalEnvFile(contents) {
112
- const env = {};
113
- const lines = contents.split(/\r?\n/);
114
- for (let index = 0; index < lines.length; index += 1) {
115
- const trimmed = lines[index].trim();
116
- if (!trimmed || trimmed.startsWith("#")) continue;
117
- const match = (trimmed.startsWith("export ") ? trimmed.slice(7).trimStart() : trimmed).match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
118
- if (!match) throw new LocalEnvFileError(`Invalid env file line ${index + 1}`);
119
- const key = match[1];
120
- const rawValue = match[2];
121
- env[key] = parseEnvValue(rawValue, index + 1);
122
- }
123
- return env;
124
- }
125
- function mergeRivusDaemonEnv(fileEnv, overrideEnv) {
126
- const merged = { ...fileEnv };
127
- for (const [key, value] of Object.entries(overrideEnv)) if (value !== void 0) merged[key] = value;
128
- return merged;
129
- }
130
- function parseEnvValue(rawValue, lineNumber) {
131
- const value = rawValue.trim();
132
- if (!value) return "";
133
- if (value.startsWith("'")) {
134
- if (!value.endsWith("'")) throw new LocalEnvFileError(`Invalid single-quoted env value on line ${lineNumber}`);
135
- return value.slice(1, -1).replaceAll("'\\''", "'");
136
- }
137
- if (value.startsWith("\"")) {
138
- if (!value.endsWith("\"")) throw new LocalEnvFileError(`Invalid double-quoted env value on line ${lineNumber}`);
139
- return unescapeDoubleQuotedValue(value.slice(1, -1));
140
- }
141
- return value;
142
- }
143
- function unescapeDoubleQuotedValue(value) {
144
- return value.replace(/\\(["\\nrt])/g, (_match, escaped) => {
145
- switch (escaped) {
146
- case "n": return "\n";
147
- case "r": return "\r";
148
- case "t": return " ";
149
- default: return escaped;
150
- }
151
- });
572
+ return Effect.fail(new RivusDaemonConfigError(variable, `${variable} is required`));
573
+ }
574
+ function optionalThinkingLevel(value) {
575
+ const normalized = optional(value);
576
+ if (!normalized) return Effect.succeed(void 0);
577
+ if (THINKING_LEVELS$1.has(normalized)) return Effect.succeed(normalized);
578
+ return Effect.fail(new RivusDaemonConfigError("PI_THINKING_LEVEL", "PI_THINKING_LEVEL must be one of off, minimal, low, medium, high, xhigh"));
152
579
  }
153
580
  //#endregion
154
581
  //#region src/infrastructure/config/openclaw-env-import.ts
@@ -294,265 +721,58 @@ function readModelId(modelReference) {
294
721
  const separator = modelReference.indexOf("/");
295
722
  return separator >= 0 && separator < modelReference.length - 1 ? modelReference.slice(separator + 1) : void 0;
296
723
  }
297
- function inferFeishuBaseUrl(domain) {
298
- return domain === "lark" ? DEFAULT_LARK_BASE_URL : DEFAULT_FEISHU_BASE_URL;
299
- }
300
- function quoteEnvValue(value) {
301
- return `'${value.replaceAll("'", "'\\''")}'`;
302
- }
303
- function requiredString(record, key, path) {
304
- const value = optionalString(record[key]);
305
- if (!value) throw new OpenClawEnvImportError(`${path} is required`);
306
- return value;
307
- }
308
- function optionalString(value) {
309
- return typeof value === "string" && value.trim() ? value.trim() : void 0;
310
- }
311
- function asRecord(value, path) {
312
- const record = optionalRecord(value);
313
- if (!record) throw new OpenClawEnvImportError(`${path} must be an object`);
314
- return record;
315
- }
316
- function optionalRecord(value) {
317
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
318
- }
319
- function readPath(record, path) {
320
- let value = record;
321
- for (const segment of path) {
322
- const current = optionalRecord(value);
323
- if (!current) return;
324
- value = current[segment];
325
- }
326
- return value;
327
- }
328
- //#endregion
329
- //#region src/application/daemon/rivus-daemon-shutdown-controller.ts
330
- const DEFAULT_SIGNALS = ["SIGINT", "SIGTERM"];
331
- function createRivusDaemonShutdownController(options) {
332
- let shutdown;
333
- const handle = (signal) => {
334
- shutdown ??= Effect.runPromise(options.daemon.stop()).then(() => options.onStopped?.(signal)).catch(async (error) => {
335
- await options.onError?.(error, signal);
336
- throw error;
337
- });
338
- return shutdown;
339
- };
340
- return {
341
- handle,
342
- install: () => {
343
- for (const signal of options.signals ?? DEFAULT_SIGNALS) options.signalSource.on(signal, () => {
344
- handle(signal);
345
- });
346
- },
347
- stopping: () => shutdown !== void 0
348
- };
349
- }
350
- //#endregion
351
- //#region src/application/plugin/rivus-automation-runtime-definition.ts
352
- function resolveRivusAutomationRuntimeDefinition(definition, requestedToolIds, requestedSkillIds) {
353
- const toolIds = Object.freeze([...new Set(requestedToolIds)].sort());
354
- const toolsById = new Map(definition.tools.map((tool) => [tool.id, tool]));
355
- const tools = Object.freeze(toolIds.map((toolId) => {
356
- const tool = toolsById.get(toolId);
357
- if (!tool) throw new Error(`Automation Runtime requests ungranted tool: ${toolId}`);
358
- return tool;
359
- }));
360
- const skillIds = Object.freeze([...new Set(requestedSkillIds)].sort());
361
- const skillsById = new Map(definition.skills.map((skill) => [skill.id, skill]));
362
- const skills = Object.freeze(skillIds.map((skillId) => {
363
- const skill = skillsById.get(skillId);
364
- if (!skill) throw new Error(`Automation Runtime requests ungranted skill: ${skillId}`);
365
- return skill;
366
- }));
367
- return deepFreeze({
368
- ...definition,
369
- memory: {
370
- scopes: [],
371
- tool: false
372
- },
373
- skillGrantSet: {
374
- revision: createHash("sha256").update(JSON.stringify({
375
- parentRevision: definition.skillGrantSet.revision,
376
- skillIds
377
- })).digest("hex"),
378
- skillIds
379
- },
380
- skills,
381
- toolGrantSet: {
382
- revision: createHash("sha256").update(JSON.stringify({
383
- parentRevision: definition.toolGrantSet.revision,
384
- toolIds
385
- })).digest("hex"),
386
- toolIds
387
- },
388
- tools
389
- });
390
- }
391
- //#endregion
392
- //#region src/application/plugin/rivus-plugin-loader.ts
393
- var RivusPluginLoadError = class extends Error {
394
- pluginId;
395
- moduleSpecifier;
396
- name = "RivusPluginLoadError";
397
- constructor(pluginId, moduleSpecifier, message, options) {
398
- super(message, options);
399
- this.pluginId = pluginId;
400
- this.moduleSpecifier = moduleSpecifier;
401
- }
402
- };
403
- async function loadRivusDeployment(options) {
404
- validateRivusDeploymentManifest(options.manifest);
405
- const catalog = createRivusPluginCatalog();
406
- const pluginStatuses = [];
407
- const statusByPlugin = /* @__PURE__ */ new Map();
408
- for (const declaration of options.manifest.plugins) try {
409
- const plugin = await resolvePluginExport(await options.loadModule({
410
- deploymentRoot: options.deploymentRoot,
411
- module: declaration.module,
412
- pluginId: declaration.id
413
- }));
414
- if (plugin.manifest.id !== declaration.id) throw new Error(`plugin manifest id ${plugin.manifest.id} does not match declaration ${declaration.id}`);
415
- catalog.registerPlugin(plugin);
416
- const status = Object.freeze({
417
- id: declaration.id,
418
- module: declaration.module,
419
- required: declaration.required,
420
- status: "loaded",
421
- version: plugin.manifest.version
422
- });
423
- pluginStatuses.push(status);
424
- statusByPlugin.set(declaration.id, status);
425
- } catch (cause) {
426
- const message = cause instanceof Error ? cause.message : String(cause);
427
- if (declaration.required) throw new RivusPluginLoadError(declaration.id, declaration.module, `required plugin ${declaration.id} failed to load: ${message}`, { cause });
428
- const status = Object.freeze({
429
- error: message,
430
- id: declaration.id,
431
- module: declaration.module,
432
- required: false,
433
- status: "failed"
434
- });
435
- pluginStatuses.push(status);
436
- statusByPlugin.set(declaration.id, status);
437
- }
438
- const agentStatuses = [];
439
- const definitions = [];
440
- for (const agent of options.manifest.agents) {
441
- const pluginStatus = statusByPlugin.get(agent.pluginId);
442
- if (pluginStatus.status === "failed") {
443
- agentStatuses.push(Object.freeze({
444
- agentId: agent.agentId,
445
- pluginId: agent.pluginId,
446
- profileId: agent.profileId,
447
- reason: `plugin ${agent.pluginId} is unavailable: ${pluginStatus.error}`,
448
- status: "disabled"
449
- }));
450
- continue;
451
- }
452
- try {
453
- const definition = resolveRivusAgentDefinition(catalog, agent);
454
- definitions.push(definition);
455
- agentStatuses.push(Object.freeze({
456
- agentId: agent.agentId,
457
- definition,
458
- pluginId: agent.pluginId,
459
- profileId: agent.profileId,
460
- status: "enabled"
461
- }));
462
- } catch (cause) {
463
- const declaration = options.manifest.plugins.find(({ id }) => id === agent.pluginId);
464
- const message = cause instanceof Error ? cause.message : String(cause);
465
- if (declaration.required) throw new RivusPluginLoadError(declaration.id, declaration.module, `deployment ${agent.agentId} failed to resolve: ${message}`, { cause });
466
- agentStatuses.push(Object.freeze({
467
- agentId: agent.agentId,
468
- pluginId: agent.pluginId,
469
- profileId: agent.profileId,
470
- reason: message,
471
- status: "disabled"
472
- }));
473
- }
474
- }
475
- const agentStatusById = new Map(agentStatuses.map((agent) => [agent.agentId, agent]));
476
- const automationTemplates = new Map(catalog.snapshot().automations.map((template) => [template.id, template]));
477
- const automationDefinitions = [];
478
- for (const automation of options.manifest.automations ?? []) {
479
- const agent = agentStatusById.get(automation.agentId);
480
- if (!agent || agent.status === "disabled" || !agent.definition) continue;
481
- const template = automationTemplates.get(automation.templateId);
482
- if (!template) throw new Error(`automation ${automation.id} references unknown template: ${automation.templateId}`);
483
- if (template.pluginId !== agent.pluginId || template.profileId !== agent.profileId) throw new Error(`automation ${automation.id} template is not owned by agent profile ${agent.profileId}`);
484
- automationDefinitions.push(deepFreeze({
485
- ...automation,
486
- runtimeDefinition: resolveRivusAutomationRuntimeDefinition(agent.definition, template.requestedToolIds, template.requestedSkillIds),
487
- template
488
- }));
489
- }
490
- return Object.freeze({
491
- agents: Object.freeze(agentStatuses),
492
- automationDefinitions: Object.freeze(automationDefinitions),
493
- catalog,
494
- definitions: Object.freeze(definitions),
495
- manifest: options.manifest,
496
- plugins: Object.freeze(pluginStatuses)
497
- });
498
- }
499
- function validateRivusDeploymentManifest(manifest) {
500
- const pluginIds = /* @__PURE__ */ new Set();
501
- for (const plugin of manifest.plugins) {
502
- if (pluginIds.has(plugin.id)) throw new Error(`duplicate plugin declaration: ${plugin.id}`);
503
- validateModuleSpecifier(plugin.module);
504
- pluginIds.add(plugin.id);
505
- }
506
- const agentIds = /* @__PURE__ */ new Set();
507
- const agentById = /* @__PURE__ */ new Map();
508
- for (const agent of manifest.agents) {
509
- if (agentIds.has(agent.agentId)) throw new Error(`duplicate agent deployment: ${agent.agentId}`);
510
- agentIds.add(agent.agentId);
511
- if (!pluginIds.has(agent.pluginId)) throw new Error(`agent ${agent.agentId} references undeclared plugin: ${agent.pluginId}`);
512
- agentById.set(agent.agentId, agent);
513
- }
514
- const automationIds = /* @__PURE__ */ new Set();
515
- for (const automation of manifest.automations ?? []) {
516
- if (automationIds.has(automation.id)) throw new Error(`duplicate automation binding: ${automation.id}`);
517
- automationIds.add(automation.id);
518
- const agent = agentById.get(automation.agentId);
519
- if (!agent) throw new Error(`automation ${automation.id} references unknown agent: ${automation.agentId}`);
520
- const endpoint = manifest.endpoints.find(({ id }) => id === automation.delivery.endpointId);
521
- if (!endpoint) throw new Error(`automation ${automation.id} references unknown delivery endpoint: ${automation.delivery.endpointId}`);
522
- if (endpoint.agentId !== agent.agentId) throw new Error(`automation ${automation.id} delivery endpoint is bound to a different agent`);
523
- }
524
- const endpointIds = /* @__PURE__ */ new Set();
525
- const sessionNamespaces = /* @__PURE__ */ new Set();
526
- for (const endpoint of manifest.endpoints) {
527
- if (endpointIds.has(endpoint.id)) throw new Error(`duplicate endpoint binding: ${endpoint.id}`);
528
- endpointIds.add(endpoint.id);
529
- if (sessionNamespaces.has(endpoint.sessionNamespace)) throw new Error(`duplicate endpoint session namespace: ${endpoint.sessionNamespace}`);
530
- sessionNamespaces.add(endpoint.sessionNamespace);
531
- const agent = agentById.get(endpoint.agentId);
532
- if (!agent) throw new Error(`endpoint ${endpoint.id} references unknown agent: ${endpoint.agentId}`);
533
- if (!agent.endpointIds.includes(endpoint.id)) throw new Error(`endpoint ${endpoint.id} is not declared by agent ${endpoint.agentId}`);
534
- }
535
- for (const agent of manifest.agents) for (const endpointId of agent.endpointIds) {
536
- const endpoint = manifest.endpoints.find(({ id }) => id === endpointId);
537
- if (!endpoint) throw new Error(`agent ${agent.agentId} references unknown endpoint: ${endpointId}`);
538
- if (endpoint.agentId !== agent.agentId) throw new Error(`endpoint ${endpointId} is bound to a different agent`);
539
- }
540
- const defaultAgent = agentById.get(manifest.defaultAgentId);
541
- if (!defaultAgent) throw new Error(`default agent does not exist: ${manifest.defaultAgentId}`);
542
- const defaultEndpoint = manifest.endpoints.find(({ id }) => id === manifest.defaultEndpointId);
543
- if (!defaultEndpoint) throw new Error(`default endpoint does not exist: ${manifest.defaultEndpointId}`);
544
- if (defaultEndpoint.agentId !== defaultAgent.agentId) throw new Error("default endpoint is not bound to the default agent");
545
- if (!defaultEndpoint.enabled) throw new Error("default endpoint must be enabled");
724
+ function inferFeishuBaseUrl(domain) {
725
+ return domain === "lark" ? DEFAULT_LARK_BASE_URL : DEFAULT_FEISHU_BASE_URL;
546
726
  }
547
- function validateModuleSpecifier(moduleSpecifier) {
548
- if (moduleSpecifier.trim() === "" || isAbsolute(moduleSpecifier) || /^[a-z][a-z+.-]*:/i.test(moduleSpecifier) || moduleSpecifier.includes("\0")) throw new Error(`invalid plugin module specifier: ${moduleSpecifier}`);
549
- if ((moduleSpecifier.startsWith("./") || moduleSpecifier.startsWith("../")) && moduleSpecifier.split(/[\\/]/).includes("..")) throw new Error(`plugin module escapes deployment root: ${moduleSpecifier}`);
727
+ function quoteEnvValue(value) {
728
+ return `'${value.replaceAll("'", "'\\''")}'`;
550
729
  }
551
- async function resolvePluginExport(module) {
552
- const candidate = "default" in module ? module.default : module;
553
- const plugin = typeof candidate === "function" ? await candidate() : candidate;
554
- if (plugin === null || typeof plugin !== "object" || !("manifest" in plugin) || !("register" in plugin) || typeof plugin.register !== "function") throw new Error("plugin module default export is not a RivusPlugin or factory");
555
- return plugin;
730
+ function requiredString(record, key, path) {
731
+ const value = optionalString(record[key]);
732
+ if (!value) throw new OpenClawEnvImportError(`${path} is required`);
733
+ return value;
734
+ }
735
+ function optionalString(value) {
736
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
737
+ }
738
+ function asRecord(value, path) {
739
+ const record = optionalRecord(value);
740
+ if (!record) throw new OpenClawEnvImportError(`${path} must be an object`);
741
+ return record;
742
+ }
743
+ function optionalRecord(value) {
744
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
745
+ }
746
+ function readPath(record, path) {
747
+ let value = record;
748
+ for (const segment of path) {
749
+ const current = optionalRecord(value);
750
+ if (!current) return;
751
+ value = current[segment];
752
+ }
753
+ return value;
754
+ }
755
+ //#endregion
756
+ //#region src/application/daemon/rivus-daemon-shutdown-controller.ts
757
+ const DEFAULT_SIGNALS = ["SIGINT", "SIGTERM"];
758
+ function createRivusDaemonShutdownController(options) {
759
+ let shutdown;
760
+ const handle = (signal) => {
761
+ shutdown ??= Effect.runPromise(options.daemon.stop()).then(() => options.onStopped?.(signal)).catch(async (error) => {
762
+ await options.onError?.(error, signal);
763
+ throw error;
764
+ });
765
+ return shutdown;
766
+ };
767
+ return {
768
+ handle,
769
+ install: () => {
770
+ for (const signal of options.signals ?? DEFAULT_SIGNALS) options.signalSource.on(signal, () => {
771
+ handle(signal);
772
+ });
773
+ },
774
+ stopping: () => shutdown !== void 0
775
+ };
556
776
  }
557
777
  //#endregion
558
778
  //#region src/application/support/stable-id.ts
@@ -781,10 +1001,11 @@ async function createRivusDeploymentDaemon(options) {
781
1001
  const slotById = new Map(slots.map((slot) => [slot.definition.id, slot]));
782
1002
  let lifecycle = "stopped";
783
1003
  const canRunIntake = () => lifecycle === "running" || lifecycle === "degraded";
1004
+ const canRunEndpointIntake = (slot) => (canRunIntake() || lifecycle === "starting") && (slot.lifecycle === "running" || slot.lifecycle === "starting") && (slot.adapter?.running() ?? false);
784
1005
  const handleEndpoint = async (endpointId, input) => {
785
1006
  const slot = slotById.get(endpointId);
786
1007
  if (!slot) throw new RivusDeploymentDaemonLifecycleError(`unknown endpoint: ${endpointId}`);
787
- if (!canRunIntake() || slot.lifecycle !== "running" || !(slot.adapter?.running() ?? false)) throw new RivusDeploymentDaemonLifecycleError(`endpoint ${endpointId} cannot accept intake while ${slot.lifecycle}`);
1008
+ if (!canRunEndpointIntake(slot)) throw new RivusDeploymentDaemonLifecycleError(`endpoint ${endpointId} cannot accept intake while ${slot.lifecycle}`);
788
1009
  return host.handleEndpoint(endpointId, input);
789
1010
  };
790
1011
  const endpointStatus = (slot) => Object.freeze({
@@ -946,204 +1167,6 @@ async function stopSlots(slots, errors) {
946
1167
  }
947
1168
  }
948
1169
  //#endregion
949
- //#region src/infrastructure/config/rivus-deployment-manifest.ts
950
- var RivusDeploymentManifestError = class extends Error {
951
- manifestPath;
952
- name = "RivusDeploymentManifestError";
953
- constructor(manifestPath, message, options) {
954
- super(message, options);
955
- this.manifestPath = manifestPath;
956
- }
957
- };
958
- async function loadRivusDeploymentManifest(manifestPath, options = {}) {
959
- const maxBytes = options.maxBytes ?? 1024 * 1024;
960
- try {
961
- const metadata = await stat(manifestPath);
962
- if (!metadata.isFile()) throw new Error("deployment manifest must be a regular file");
963
- if (metadata.size > maxBytes) throw new Error(`deployment manifest exceeds ${maxBytes} byte limit`);
964
- const bytes = await readFile(manifestPath);
965
- const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
966
- return parseManifest(JSON.parse(text));
967
- } catch (cause) {
968
- if (cause instanceof RivusDeploymentManifestError) throw cause;
969
- throw new RivusDeploymentManifestError(manifestPath, `failed to load Rivus deployment manifest: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
970
- }
971
- }
972
- function parseManifest(value) {
973
- const root = record(value, "manifest");
974
- exactKeys(root, [
975
- "agents",
976
- "automations",
977
- "defaultAgentId",
978
- "defaultEndpointId",
979
- "endpoints",
980
- "plugins"
981
- ], "manifest", ["automations"]);
982
- const plugins = array(root.plugins, "manifest.plugins").map((entry, index) => {
983
- const plugin = record(entry, `manifest.plugins[${index}]`);
984
- exactKeys(plugin, [
985
- "id",
986
- "module",
987
- "required"
988
- ], `manifest.plugins[${index}]`);
989
- return Object.freeze({
990
- id: string(plugin.id, `manifest.plugins[${index}].id`),
991
- module: string(plugin.module, `manifest.plugins[${index}].module`),
992
- required: boolean(plugin.required, `manifest.plugins[${index}].required`)
993
- });
994
- });
995
- const agents = array(root.agents, "manifest.agents").map((entry, index) => {
996
- const agent = record(entry, `manifest.agents[${index}]`);
997
- exactKeys(agent, [
998
- "agentId",
999
- "endpointIds",
1000
- "memory",
1001
- "pluginId",
1002
- "profileId",
1003
- "skills",
1004
- "tools"
1005
- ], `manifest.agents[${index}]`, ["memory"]);
1006
- const memory = agent.memory === void 0 ? void 0 : record(agent.memory, `manifest.agents[${index}].memory`);
1007
- if (memory) exactKeys(memory, ["scopes", "tool"], `manifest.agents[${index}].memory`);
1008
- const skills = record(agent.skills, `manifest.agents[${index}].skills`);
1009
- exactKeys(skills, ["allow"], `manifest.agents[${index}].skills`);
1010
- const tools = record(agent.tools, `manifest.agents[${index}].tools`);
1011
- exactKeys(tools, ["allow"], `manifest.agents[${index}].tools`);
1012
- return Object.freeze({
1013
- agentId: string(agent.agentId, `manifest.agents[${index}].agentId`),
1014
- endpointIds: Object.freeze(array(agent.endpointIds, `manifest.agents[${index}].endpointIds`).map((item, itemIndex) => string(item, `manifest.agents[${index}].endpointIds[${itemIndex}]`))),
1015
- ...memory ? { memory: Object.freeze({
1016
- scopes: Object.freeze(array(memory.scopes, `manifest.agents[${index}].memory.scopes`).map((item, itemIndex) => memoryScope(item, `manifest.agents[${index}].memory.scopes[${itemIndex}]`))),
1017
- tool: boolean(memory.tool, `manifest.agents[${index}].memory.tool`)
1018
- }) } : {},
1019
- pluginId: string(agent.pluginId, `manifest.agents[${index}].pluginId`),
1020
- profileId: string(agent.profileId, `manifest.agents[${index}].profileId`),
1021
- skills: Object.freeze({ allow: Object.freeze(array(skills.allow, `manifest.agents[${index}].skills.allow`).map((item, itemIndex) => string(item, `manifest.agents[${index}].skills.allow[${itemIndex}]`))) }),
1022
- tools: Object.freeze({ allow: Object.freeze(array(tools.allow, `manifest.agents[${index}].tools.allow`).map((item, itemIndex) => string(item, `manifest.agents[${index}].tools.allow[${itemIndex}]`))) })
1023
- });
1024
- });
1025
- const endpoints = array(root.endpoints, "manifest.endpoints").map((entry, index) => {
1026
- const endpoint = record(entry, `manifest.endpoints[${index}]`);
1027
- exactKeys(endpoint, [
1028
- "agentId",
1029
- "baseUrl",
1030
- "credentialRef",
1031
- "enabled",
1032
- "experimental",
1033
- "groupPolicy",
1034
- "id",
1035
- "required",
1036
- "sessionNamespace",
1037
- "streamMinIntervalMs"
1038
- ], `manifest.endpoints[${index}]`, ["experimental"]);
1039
- const experimental = endpoint.experimental === void 0 ? void 0 : record(endpoint.experimental, `manifest.endpoints[${index}].experimental`);
1040
- if (experimental) exactKeys(experimental, ["cotMessages"], `manifest.endpoints[${index}].experimental`);
1041
- return Object.freeze({
1042
- agentId: string(endpoint.agentId, `manifest.endpoints[${index}].agentId`),
1043
- baseUrl: string(endpoint.baseUrl, `manifest.endpoints[${index}].baseUrl`),
1044
- credentialRef: string(endpoint.credentialRef, `manifest.endpoints[${index}].credentialRef`),
1045
- enabled: boolean(endpoint.enabled, `manifest.endpoints[${index}].enabled`),
1046
- ...experimental ? { experimental: Object.freeze({ cotMessages: boolean(experimental.cotMessages, `manifest.endpoints[${index}].experimental.cotMessages`) }) } : {},
1047
- groupPolicy: groupPolicy(endpoint.groupPolicy, `manifest.endpoints[${index}].groupPolicy`),
1048
- id: string(endpoint.id, `manifest.endpoints[${index}].id`),
1049
- required: boolean(endpoint.required, `manifest.endpoints[${index}].required`),
1050
- sessionNamespace: string(endpoint.sessionNamespace, `manifest.endpoints[${index}].sessionNamespace`),
1051
- streamMinIntervalMs: positiveInteger(endpoint.streamMinIntervalMs, `manifest.endpoints[${index}].streamMinIntervalMs`)
1052
- });
1053
- });
1054
- const automations = array(root.automations ?? [], "manifest.automations").map((entry, index) => {
1055
- const automation = record(entry, `manifest.automations[${index}]`);
1056
- exactKeys(automation, [
1057
- "agentId",
1058
- "delivery",
1059
- "enabled",
1060
- "id",
1061
- "required",
1062
- "schedule",
1063
- "templateId",
1064
- "timeZone"
1065
- ], `manifest.automations[${index}]`);
1066
- const delivery = record(automation.delivery, `manifest.automations[${index}].delivery`);
1067
- exactKeys(delivery, [
1068
- "endpointId",
1069
- "targetRef",
1070
- "targetType"
1071
- ], `manifest.automations[${index}].delivery`);
1072
- return Object.freeze({
1073
- agentId: string(automation.agentId, `manifest.automations[${index}].agentId`),
1074
- delivery: Object.freeze({
1075
- endpointId: string(delivery.endpointId, `manifest.automations[${index}].delivery.endpointId`),
1076
- targetRef: string(delivery.targetRef, `manifest.automations[${index}].delivery.targetRef`),
1077
- targetType: automationTargetType(delivery.targetType, `manifest.automations[${index}].delivery.targetType`)
1078
- }),
1079
- enabled: boolean(automation.enabled, `manifest.automations[${index}].enabled`),
1080
- id: string(automation.id, `manifest.automations[${index}].id`),
1081
- required: boolean(automation.required, `manifest.automations[${index}].required`),
1082
- schedule: string(automation.schedule, `manifest.automations[${index}].schedule`),
1083
- templateId: string(automation.templateId, `manifest.automations[${index}].templateId`),
1084
- timeZone: string(automation.timeZone, `manifest.automations[${index}].timeZone`)
1085
- });
1086
- });
1087
- return Object.freeze({
1088
- agents: Object.freeze(agents),
1089
- automations: Object.freeze(automations),
1090
- defaultAgentId: string(root.defaultAgentId, "manifest.defaultAgentId"),
1091
- defaultEndpointId: string(root.defaultEndpointId, "manifest.defaultEndpointId"),
1092
- endpoints: Object.freeze(endpoints),
1093
- plugins: Object.freeze(plugins)
1094
- });
1095
- }
1096
- function record(value, path) {
1097
- if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error(`${path} must be an object`);
1098
- return value;
1099
- }
1100
- function array(value, path) {
1101
- if (!Array.isArray(value)) throw new Error(`${path} must be an array`);
1102
- return value;
1103
- }
1104
- function string(value, path) {
1105
- if (typeof value !== "string" || value.trim() === "") throw new Error(`${path} must be a non-empty string`);
1106
- return value;
1107
- }
1108
- function boolean(value, path) {
1109
- if (typeof value !== "boolean") throw new Error(`${path} must be a boolean`);
1110
- return value;
1111
- }
1112
- function positiveInteger(value, path) {
1113
- if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`${path} must be a positive integer`);
1114
- return value;
1115
- }
1116
- function memoryScope(value, path) {
1117
- if (typeof value !== "string" || !MEMORY_SCOPES.includes(value)) throw new Error(`${path} must be conversation, agent-private, or shared-user-profile`);
1118
- return value;
1119
- }
1120
- function groupPolicy(value, path) {
1121
- if (value !== "mention-only" && value !== "ignore-unmentioned" && value !== "default-responder") throw new Error(`${path} must be mention-only, ignore-unmentioned, or default-responder`);
1122
- return value;
1123
- }
1124
- function automationTargetType(value, path) {
1125
- if (value !== "chat_id" && value !== "open_id" && value !== "user_id" && value !== "union_id" && value !== "email") throw new Error(`${path} must be chat_id, open_id, user_id, union_id, or email`);
1126
- return value;
1127
- }
1128
- function exactKeys(value, allowed, path, optional = []) {
1129
- const unexpected = Object.keys(value).find((key) => !allowed.includes(key));
1130
- if (unexpected) throw new Error(`${path} contains unsupported field: ${unexpected}`);
1131
- const missing = allowed.find((key) => !optional.includes(key) && !Object.hasOwn(value, key));
1132
- if (missing) throw new Error(`${path} is missing required field: ${missing}`);
1133
- }
1134
- //#endregion
1135
- //#region src/infrastructure/plugin/node-rivus-plugin-module-loader.ts
1136
- async function loadNodeRivusPluginModule(request) {
1137
- const deploymentRoot = await realpath(request.deploymentRoot);
1138
- const resolvedRealpath = await realpath(createRequire(join(deploymentRoot, "package.json")).resolve(request.module));
1139
- if (!isWithin(deploymentRoot, resolvedRealpath)) throw new Error(`plugin module ${request.module} resolves outside deployment root: ${resolvedRealpath}`);
1140
- return await import(pathToFileURL(resolvedRealpath).href);
1141
- }
1142
- function isWithin(root, candidate) {
1143
- const child = relative(root, candidate);
1144
- return child === "" || !child.startsWith(`..${sep}`) && child !== ".." && !isAbsolute(child);
1145
- }
1146
- //#endregion
1147
1170
  //#region src/infrastructure/runtime/configured-rivus-deployment-daemon.ts
1148
1171
  async function createConfiguredRivusDeploymentDaemon(options) {
1149
1172
  const manifest = await loadRivusDeploymentManifest(options.manifestPath, options.manifestOptions);
@@ -1452,6 +1475,10 @@ const USAGE = `Usage: rivus --bootstrap <module> [--manifest <rivus.config.json>
1452
1475
 
1453
1476
  Starts a local Rivus Agent daemon from an injected bootstrap module.
1454
1477
 
1478
+ Project commands:
1479
+ rivus init [directory]
1480
+ rivus doctor [directory] [--env-file <path>]
1481
+
1455
1482
  Options:
1456
1483
  --bootstrap <module> Module exporting createRivusDaemonProcess(context)
1457
1484
  --manifest <path> Start the manifest-driven multi-agent deployment; bootstrap exports createRivusDeploymentAdapters(context)
@@ -2596,4 +2623,4 @@ function hasRecoveryRunner(daemon) {
2596
2623
  return typeof daemon.openRecoveryControl === "function";
2597
2624
  }
2598
2625
  //#endregion
2599
- export { OpenClawEnvImportError as C, loadRivusDaemonConfig as D, RivusDaemonConfigError as E, createRivusDaemonShutdownController as S, formatRivusEnvFile as T, createAgentInstanceRegistry as _, RivusDeploymentManifestError as a, loadRivusDeployment as b, RivusDeploymentDaemonLifecycleError as c, InvalidRivusEndpointBinding as d, createRivusAgentHost as f, AgentInstanceConflict as g, createAgentRuntimePool as h, loadNodeRivusPluginModule as i, RivusDeploymentReadinessError as l, AgentRuntimeDisposed as m, createRivusDeploymentCliProcess as n, loadRivusDeploymentManifest as o, AgentInstanceBusy as p, createConfiguredRivusDeploymentDaemon as r, RivusDeploymentAutomationReadinessError as s, runRivusDaemonCli as t, createRivusDeploymentDaemon as u, createStableId as v, createRivusEnvFromOpenClawConfig as w, validateRivusDeploymentManifest as x, RivusPluginLoadError as y };
2626
+ export { loadRivusDeployment as A, resolveNodeRivusPluginModulePath as C, resolveFeishuEndpointCredentials as D, FeishuEndpointCredentialError as E, loadMergedLocalEnvFile as O, loadNodeRivusPluginModule as S, loadRivusDeploymentManifest as T, OpenClawEnvImportError as _, RivusDeploymentDaemonLifecycleError as a, RivusDaemonConfigError as b, InvalidRivusEndpointBinding as c, AgentRuntimeDisposed as d, createAgentRuntimePool as f, createRivusDaemonShutdownController as g, createStableId as h, RivusDeploymentAutomationReadinessError as i, validateRivusDeploymentManifest as j, RivusPluginLoadError as k, createRivusAgentHost as l, createAgentInstanceRegistry as m, createRivusDeploymentCliProcess as n, RivusDeploymentReadinessError as o, AgentInstanceConflict as p, createConfiguredRivusDeploymentDaemon as r, createRivusDeploymentDaemon as s, runRivusDaemonCli as t, AgentInstanceBusy as u, createRivusEnvFromOpenClawConfig as v, RivusDeploymentManifestError as w, loadRivusDaemonConfig as x, formatRivusEnvFile as y };