@rivus/agent 0.1.1 → 0.4.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 +19 -4
- package/dist/acp.d.ts +98 -0
- package/dist/acp.js +436 -0
- package/dist/agent-loop.d.ts +420 -0
- package/dist/agent-loop.js +118 -0
- package/dist/agent-memory.d.ts +98 -0
- package/dist/cli.js +508 -5
- package/dist/index.d.ts +15 -427
- package/dist/index.js +131 -180
- package/dist/rivus-daemon-cli.js +558 -527
- package/dist/rivus-plugin-testkit.d.ts +3 -98
- package/dist/rivus-plugin-testkit.js +2 -2
- package/examples/a-share-briefing-analysis.mjs +93 -0
- package/examples/a-share-briefing-renderer.mjs +257 -0
- package/examples/a-share-index-evidence.mjs +99 -0
- package/examples/a-share-market-briefing.mjs +83 -0
- package/examples/a-share-market-date.mjs +10 -0
- package/examples/a-share-overseas-evidence.mjs +86 -0
- package/examples/a-share-policy-evidence.mjs +145 -0
- package/examples/a-share-provider-response.mjs +21 -0
- package/examples/a-share-sector-evidence.mjs +70 -0
- package/examples/acp-stdio-proxy.mjs +55 -0
- package/examples/current-weather.mjs +6 -32
- package/examples/https-response-reader.mjs +36 -0
- package/examples/pi-feishu-deployment.bootstrap.ts +2 -2
- package/examples/rivus-agents.plugin.mjs +55 -3
- package/examples/rivus-starter.plugin.mjs +45 -0
- package/examples/rivus.config.json +30 -1
- package/package.json +17 -7
package/dist/rivus-daemon-cli.js
CHANGED
|
@@ -1,11 +1,496 @@
|
|
|
1
1
|
import { i as MEMORY_SCOPES, n as resolveRivusAgentDefinition, r as deepFreeze, t as createRivusPluginCatalog } from "./rivus-plugin-registry.js";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
+
import { Effect } from "effect";
|
|
4
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
5
|
import { pathToFileURL } from "node:url";
|
|
4
6
|
import { dirname, isAbsolute, join, relative, sep } from "node:path";
|
|
5
|
-
import { Effect } from "effect";
|
|
6
7
|
import { open, readFile, realpath, stat } from "node:fs/promises";
|
|
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 deliveryEndpoint = options.manifest.endpoints.find(({ id }) => id === automation.delivery.endpointId);
|
|
140
|
+
const presentationAgent = agentStatusById.get(deliveryEndpoint.agentId);
|
|
141
|
+
if (!presentationAgent || presentationAgent.status === "disabled" || !presentationAgent.definition) continue;
|
|
142
|
+
const template = automationTemplates.get(automation.templateId);
|
|
143
|
+
if (!template) throw new Error(`automation ${automation.id} references unknown template: ${automation.templateId}`);
|
|
144
|
+
if (template.pluginId !== agent.pluginId || template.profileId !== agent.profileId) throw new Error(`automation ${automation.id} template is not owned by agent profile ${agent.profileId}`);
|
|
145
|
+
automationDefinitions.push(deepFreeze({
|
|
146
|
+
...automation,
|
|
147
|
+
runtimeDefinition: resolveRivusAutomationRuntimeDefinition(agent.definition, template.requestedToolIds, template.requestedSkillIds),
|
|
148
|
+
template
|
|
149
|
+
}));
|
|
150
|
+
}
|
|
151
|
+
return Object.freeze({
|
|
152
|
+
agents: Object.freeze(agentStatuses),
|
|
153
|
+
automationDefinitions: Object.freeze(automationDefinitions),
|
|
154
|
+
catalog,
|
|
155
|
+
definitions: Object.freeze(definitions),
|
|
156
|
+
manifest: options.manifest,
|
|
157
|
+
plugins: Object.freeze(pluginStatuses)
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
function validateRivusDeploymentManifest(manifest) {
|
|
161
|
+
const pluginIds = /* @__PURE__ */ new Set();
|
|
162
|
+
for (const plugin of manifest.plugins) {
|
|
163
|
+
if (pluginIds.has(plugin.id)) throw new Error(`duplicate plugin declaration: ${plugin.id}`);
|
|
164
|
+
validateModuleSpecifier(plugin.module);
|
|
165
|
+
pluginIds.add(plugin.id);
|
|
166
|
+
}
|
|
167
|
+
const agentIds = /* @__PURE__ */ new Set();
|
|
168
|
+
const agentById = /* @__PURE__ */ new Map();
|
|
169
|
+
for (const agent of manifest.agents) {
|
|
170
|
+
if (agentIds.has(agent.agentId)) throw new Error(`duplicate agent deployment: ${agent.agentId}`);
|
|
171
|
+
agentIds.add(agent.agentId);
|
|
172
|
+
if (!pluginIds.has(agent.pluginId)) throw new Error(`agent ${agent.agentId} references undeclared plugin: ${agent.pluginId}`);
|
|
173
|
+
agentById.set(agent.agentId, agent);
|
|
174
|
+
}
|
|
175
|
+
const automationIds = /* @__PURE__ */ new Set();
|
|
176
|
+
for (const automation of manifest.automations ?? []) {
|
|
177
|
+
if (automationIds.has(automation.id)) throw new Error(`duplicate automation binding: ${automation.id}`);
|
|
178
|
+
automationIds.add(automation.id);
|
|
179
|
+
if (!agentById.get(automation.agentId)) throw new Error(`automation ${automation.id} references unknown agent: ${automation.agentId}`);
|
|
180
|
+
const endpoint = manifest.endpoints.find(({ id }) => id === automation.delivery.endpointId);
|
|
181
|
+
if (!endpoint) throw new Error(`automation ${automation.id} references unknown delivery endpoint: ${automation.delivery.endpointId}`);
|
|
182
|
+
if (automation.enabled && !endpoint.enabled) throw new Error(`automation ${automation.id} delivery endpoint must be enabled`);
|
|
183
|
+
}
|
|
184
|
+
const endpointIds = /* @__PURE__ */ new Set();
|
|
185
|
+
const sessionNamespaces = /* @__PURE__ */ new Set();
|
|
186
|
+
for (const endpoint of manifest.endpoints) {
|
|
187
|
+
if (endpointIds.has(endpoint.id)) throw new Error(`duplicate endpoint binding: ${endpoint.id}`);
|
|
188
|
+
endpointIds.add(endpoint.id);
|
|
189
|
+
if (sessionNamespaces.has(endpoint.sessionNamespace)) throw new Error(`duplicate endpoint session namespace: ${endpoint.sessionNamespace}`);
|
|
190
|
+
sessionNamespaces.add(endpoint.sessionNamespace);
|
|
191
|
+
const agent = agentById.get(endpoint.agentId);
|
|
192
|
+
if (!agent) throw new Error(`endpoint ${endpoint.id} references unknown agent: ${endpoint.agentId}`);
|
|
193
|
+
if (!agent.endpointIds.includes(endpoint.id)) throw new Error(`endpoint ${endpoint.id} is not declared by agent ${endpoint.agentId}`);
|
|
194
|
+
}
|
|
195
|
+
for (const agent of manifest.agents) for (const endpointId of agent.endpointIds) {
|
|
196
|
+
const endpoint = manifest.endpoints.find(({ id }) => id === endpointId);
|
|
197
|
+
if (!endpoint) throw new Error(`agent ${agent.agentId} references unknown endpoint: ${endpointId}`);
|
|
198
|
+
if (endpoint.agentId !== agent.agentId) throw new Error(`endpoint ${endpointId} is bound to a different agent`);
|
|
199
|
+
}
|
|
200
|
+
const defaultAgent = agentById.get(manifest.defaultAgentId);
|
|
201
|
+
if (!defaultAgent) throw new Error(`default agent does not exist: ${manifest.defaultAgentId}`);
|
|
202
|
+
const defaultEndpoint = manifest.endpoints.find(({ id }) => id === manifest.defaultEndpointId);
|
|
203
|
+
if (!defaultEndpoint) throw new Error(`default endpoint does not exist: ${manifest.defaultEndpointId}`);
|
|
204
|
+
if (defaultEndpoint.agentId !== defaultAgent.agentId) throw new Error("default endpoint is not bound to the default agent");
|
|
205
|
+
if (!defaultEndpoint.enabled) throw new Error("default endpoint must be enabled");
|
|
206
|
+
}
|
|
207
|
+
function validateModuleSpecifier(moduleSpecifier) {
|
|
208
|
+
if (moduleSpecifier.trim() === "" || isAbsolute(moduleSpecifier) || /^[a-z][a-z+.-]*:/i.test(moduleSpecifier) || moduleSpecifier.includes("\0")) throw new Error(`invalid plugin module specifier: ${moduleSpecifier}`);
|
|
209
|
+
if ((moduleSpecifier.startsWith("./") || moduleSpecifier.startsWith("../")) && moduleSpecifier.split(/[\\/]/).includes("..")) throw new Error(`plugin module escapes deployment root: ${moduleSpecifier}`);
|
|
210
|
+
}
|
|
211
|
+
async function resolvePluginExport(module) {
|
|
212
|
+
const candidate = "default" in module ? module.default : module;
|
|
213
|
+
const plugin = typeof candidate === "function" ? await candidate() : candidate;
|
|
214
|
+
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");
|
|
215
|
+
return plugin;
|
|
216
|
+
}
|
|
217
|
+
//#endregion
|
|
218
|
+
//#region src/infrastructure/config/local-env-file.ts
|
|
219
|
+
var LocalEnvFileError = class extends Error {
|
|
220
|
+
constructor(message) {
|
|
221
|
+
super(message);
|
|
222
|
+
this.name = "LocalEnvFileError";
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
async function loadMergedLocalEnvFile(filePath, overrideEnv) {
|
|
226
|
+
return mergeRivusDaemonEnv(await loadLocalEnvFile(filePath), overrideEnv);
|
|
227
|
+
}
|
|
228
|
+
async function loadLocalEnvFile(filePath) {
|
|
229
|
+
return parseLocalEnvFile(await readFile(filePath, "utf8"));
|
|
230
|
+
}
|
|
231
|
+
function parseLocalEnvFile(contents) {
|
|
232
|
+
const env = {};
|
|
233
|
+
const lines = contents.split(/\r?\n/);
|
|
234
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
235
|
+
const trimmed = lines[index].trim();
|
|
236
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
237
|
+
const match = (trimmed.startsWith("export ") ? trimmed.slice(7).trimStart() : trimmed).match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
|
|
238
|
+
if (!match) throw new LocalEnvFileError(`Invalid env file line ${index + 1}`);
|
|
239
|
+
const key = match[1];
|
|
240
|
+
const rawValue = match[2];
|
|
241
|
+
env[key] = parseEnvValue(rawValue, index + 1);
|
|
242
|
+
}
|
|
243
|
+
return env;
|
|
244
|
+
}
|
|
245
|
+
function mergeRivusDaemonEnv(fileEnv, overrideEnv) {
|
|
246
|
+
const merged = { ...fileEnv };
|
|
247
|
+
for (const [key, value] of Object.entries(overrideEnv)) if (value !== void 0) merged[key] = value;
|
|
248
|
+
return merged;
|
|
249
|
+
}
|
|
250
|
+
function parseEnvValue(rawValue, lineNumber) {
|
|
251
|
+
const value = rawValue.trim();
|
|
252
|
+
if (!value) return "";
|
|
253
|
+
if (value.startsWith("'")) {
|
|
254
|
+
if (!value.endsWith("'")) throw new LocalEnvFileError(`Invalid single-quoted env value on line ${lineNumber}`);
|
|
255
|
+
return value.slice(1, -1).replaceAll("'\\''", "'");
|
|
256
|
+
}
|
|
257
|
+
if (value.startsWith("\"")) {
|
|
258
|
+
if (!value.endsWith("\"")) throw new LocalEnvFileError(`Invalid double-quoted env value on line ${lineNumber}`);
|
|
259
|
+
return unescapeDoubleQuotedValue(value.slice(1, -1));
|
|
260
|
+
}
|
|
261
|
+
return value;
|
|
262
|
+
}
|
|
263
|
+
function unescapeDoubleQuotedValue(value) {
|
|
264
|
+
return value.replace(/\\(["\\nrt])/g, (_match, escaped) => {
|
|
265
|
+
switch (escaped) {
|
|
266
|
+
case "n": return "\n";
|
|
267
|
+
case "r": return "\r";
|
|
268
|
+
case "t": return " ";
|
|
269
|
+
default: return escaped;
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
//#endregion
|
|
274
|
+
//#region src/infrastructure/config/feishu-endpoint-credentials.ts
|
|
275
|
+
var FeishuEndpointCredentialError = class extends Error {
|
|
276
|
+
name = "FeishuEndpointCredentialError";
|
|
277
|
+
};
|
|
278
|
+
function resolveFeishuEndpointCredentials(credentialRef, env) {
|
|
279
|
+
if (!credentialRef.startsWith("env:")) throw new FeishuEndpointCredentialError("Feishu endpoint credentialRef must use env:<PREFIX>");
|
|
280
|
+
const prefix = credentialRef.slice(4);
|
|
281
|
+
if (!/^[A-Z][A-Z0-9_]*$/.test(prefix)) throw new FeishuEndpointCredentialError(`Invalid environment prefix in credentialRef: ${credentialRef}`);
|
|
282
|
+
return Object.freeze({
|
|
283
|
+
appId: required$1(env, `${prefix}_APP_ID`),
|
|
284
|
+
appSecret: required$1(env, `${prefix}_APP_SECRET`)
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
function required$1(env, variable) {
|
|
288
|
+
const value = env[variable]?.trim();
|
|
289
|
+
if (!value) throw new FeishuEndpointCredentialError(`${variable} is required`);
|
|
290
|
+
return value;
|
|
291
|
+
}
|
|
292
|
+
//#endregion
|
|
293
|
+
//#region src/infrastructure/config/rivus-deployment-manifest.ts
|
|
294
|
+
var RivusDeploymentManifestError = class extends Error {
|
|
295
|
+
manifestPath;
|
|
296
|
+
name = "RivusDeploymentManifestError";
|
|
297
|
+
constructor(manifestPath, message, options) {
|
|
298
|
+
super(message, options);
|
|
299
|
+
this.manifestPath = manifestPath;
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
async function loadRivusDeploymentManifest(manifestPath, options = {}) {
|
|
303
|
+
const maxBytes = options.maxBytes ?? 1024 * 1024;
|
|
304
|
+
try {
|
|
305
|
+
const metadata = await stat(manifestPath);
|
|
306
|
+
if (!metadata.isFile()) throw new Error("deployment manifest must be a regular file");
|
|
307
|
+
if (metadata.size > maxBytes) throw new Error(`deployment manifest exceeds ${maxBytes} byte limit`);
|
|
308
|
+
const bytes = await readFile(manifestPath);
|
|
309
|
+
const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
310
|
+
return parseManifest(JSON.parse(text));
|
|
311
|
+
} catch (cause) {
|
|
312
|
+
if (cause instanceof RivusDeploymentManifestError) throw cause;
|
|
313
|
+
throw new RivusDeploymentManifestError(manifestPath, `failed to load Rivus deployment manifest: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
function parseManifest(value) {
|
|
317
|
+
const root = record(value, "manifest");
|
|
318
|
+
exactKeys(root, [
|
|
319
|
+
"agents",
|
|
320
|
+
"automations",
|
|
321
|
+
"defaultAgentId",
|
|
322
|
+
"defaultEndpointId",
|
|
323
|
+
"endpoints",
|
|
324
|
+
"plugins"
|
|
325
|
+
], "manifest", ["automations"]);
|
|
326
|
+
const plugins = array(root.plugins, "manifest.plugins").map((entry, index) => {
|
|
327
|
+
const plugin = record(entry, `manifest.plugins[${index}]`);
|
|
328
|
+
exactKeys(plugin, [
|
|
329
|
+
"id",
|
|
330
|
+
"module",
|
|
331
|
+
"required"
|
|
332
|
+
], `manifest.plugins[${index}]`);
|
|
333
|
+
return Object.freeze({
|
|
334
|
+
id: string(plugin.id, `manifest.plugins[${index}].id`),
|
|
335
|
+
module: string(plugin.module, `manifest.plugins[${index}].module`),
|
|
336
|
+
required: boolean(plugin.required, `manifest.plugins[${index}].required`)
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
const agents = array(root.agents, "manifest.agents").map((entry, index) => {
|
|
340
|
+
const agent = record(entry, `manifest.agents[${index}]`);
|
|
341
|
+
exactKeys(agent, [
|
|
342
|
+
"agentId",
|
|
343
|
+
"endpointIds",
|
|
344
|
+
"memory",
|
|
345
|
+
"pluginId",
|
|
346
|
+
"profileId",
|
|
347
|
+
"skills",
|
|
348
|
+
"tools"
|
|
349
|
+
], `manifest.agents[${index}]`, ["memory"]);
|
|
350
|
+
const memory = agent.memory === void 0 ? void 0 : record(agent.memory, `manifest.agents[${index}].memory`);
|
|
351
|
+
if (memory) exactKeys(memory, ["scopes", "tool"], `manifest.agents[${index}].memory`);
|
|
352
|
+
const skills = record(agent.skills, `manifest.agents[${index}].skills`);
|
|
353
|
+
exactKeys(skills, ["allow"], `manifest.agents[${index}].skills`);
|
|
354
|
+
const tools = record(agent.tools, `manifest.agents[${index}].tools`);
|
|
355
|
+
exactKeys(tools, ["allow"], `manifest.agents[${index}].tools`);
|
|
356
|
+
return Object.freeze({
|
|
357
|
+
agentId: string(agent.agentId, `manifest.agents[${index}].agentId`),
|
|
358
|
+
endpointIds: Object.freeze(array(agent.endpointIds, `manifest.agents[${index}].endpointIds`).map((item, itemIndex) => string(item, `manifest.agents[${index}].endpointIds[${itemIndex}]`))),
|
|
359
|
+
...memory ? { memory: Object.freeze({
|
|
360
|
+
scopes: Object.freeze(array(memory.scopes, `manifest.agents[${index}].memory.scopes`).map((item, itemIndex) => memoryScope(item, `manifest.agents[${index}].memory.scopes[${itemIndex}]`))),
|
|
361
|
+
tool: boolean(memory.tool, `manifest.agents[${index}].memory.tool`)
|
|
362
|
+
}) } : {},
|
|
363
|
+
pluginId: string(agent.pluginId, `manifest.agents[${index}].pluginId`),
|
|
364
|
+
profileId: string(agent.profileId, `manifest.agents[${index}].profileId`),
|
|
365
|
+
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}]`))) }),
|
|
366
|
+
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}]`))) })
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
const endpoints = array(root.endpoints, "manifest.endpoints").map((entry, index) => {
|
|
370
|
+
const endpoint = record(entry, `manifest.endpoints[${index}]`);
|
|
371
|
+
exactKeys(endpoint, [
|
|
372
|
+
"agentId",
|
|
373
|
+
"baseUrl",
|
|
374
|
+
"credentialRef",
|
|
375
|
+
"enabled",
|
|
376
|
+
"experimental",
|
|
377
|
+
"groupPolicy",
|
|
378
|
+
"id",
|
|
379
|
+
"required",
|
|
380
|
+
"sessionNamespace",
|
|
381
|
+
"streamMinIntervalMs"
|
|
382
|
+
], `manifest.endpoints[${index}]`, ["experimental"]);
|
|
383
|
+
const experimental = endpoint.experimental === void 0 ? void 0 : record(endpoint.experimental, `manifest.endpoints[${index}].experimental`);
|
|
384
|
+
if (experimental) exactKeys(experimental, ["cotMessages"], `manifest.endpoints[${index}].experimental`);
|
|
385
|
+
return Object.freeze({
|
|
386
|
+
agentId: string(endpoint.agentId, `manifest.endpoints[${index}].agentId`),
|
|
387
|
+
baseUrl: string(endpoint.baseUrl, `manifest.endpoints[${index}].baseUrl`),
|
|
388
|
+
credentialRef: string(endpoint.credentialRef, `manifest.endpoints[${index}].credentialRef`),
|
|
389
|
+
enabled: boolean(endpoint.enabled, `manifest.endpoints[${index}].enabled`),
|
|
390
|
+
...experimental ? { experimental: Object.freeze({ cotMessages: boolean(experimental.cotMessages, `manifest.endpoints[${index}].experimental.cotMessages`) }) } : {},
|
|
391
|
+
groupPolicy: groupPolicy(endpoint.groupPolicy, `manifest.endpoints[${index}].groupPolicy`),
|
|
392
|
+
id: string(endpoint.id, `manifest.endpoints[${index}].id`),
|
|
393
|
+
required: boolean(endpoint.required, `manifest.endpoints[${index}].required`),
|
|
394
|
+
sessionNamespace: string(endpoint.sessionNamespace, `manifest.endpoints[${index}].sessionNamespace`),
|
|
395
|
+
streamMinIntervalMs: positiveInteger(endpoint.streamMinIntervalMs, `manifest.endpoints[${index}].streamMinIntervalMs`)
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
const automations = array(root.automations ?? [], "manifest.automations").map((entry, index) => {
|
|
399
|
+
const automation = record(entry, `manifest.automations[${index}]`);
|
|
400
|
+
exactKeys(automation, [
|
|
401
|
+
"agentId",
|
|
402
|
+
"delivery",
|
|
403
|
+
"enabled",
|
|
404
|
+
"id",
|
|
405
|
+
"required",
|
|
406
|
+
"schedule",
|
|
407
|
+
"templateId",
|
|
408
|
+
"timeZone"
|
|
409
|
+
], `manifest.automations[${index}]`);
|
|
410
|
+
const delivery = record(automation.delivery, `manifest.automations[${index}].delivery`);
|
|
411
|
+
exactKeys(delivery, [
|
|
412
|
+
"endpointId",
|
|
413
|
+
"targetRef",
|
|
414
|
+
"targetType"
|
|
415
|
+
], `manifest.automations[${index}].delivery`);
|
|
416
|
+
return Object.freeze({
|
|
417
|
+
agentId: string(automation.agentId, `manifest.automations[${index}].agentId`),
|
|
418
|
+
delivery: Object.freeze({
|
|
419
|
+
endpointId: string(delivery.endpointId, `manifest.automations[${index}].delivery.endpointId`),
|
|
420
|
+
targetRef: string(delivery.targetRef, `manifest.automations[${index}].delivery.targetRef`),
|
|
421
|
+
targetType: automationTargetType(delivery.targetType, `manifest.automations[${index}].delivery.targetType`)
|
|
422
|
+
}),
|
|
423
|
+
enabled: boolean(automation.enabled, `manifest.automations[${index}].enabled`),
|
|
424
|
+
id: string(automation.id, `manifest.automations[${index}].id`),
|
|
425
|
+
required: boolean(automation.required, `manifest.automations[${index}].required`),
|
|
426
|
+
schedule: string(automation.schedule, `manifest.automations[${index}].schedule`),
|
|
427
|
+
templateId: string(automation.templateId, `manifest.automations[${index}].templateId`),
|
|
428
|
+
timeZone: string(automation.timeZone, `manifest.automations[${index}].timeZone`)
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
return Object.freeze({
|
|
432
|
+
agents: Object.freeze(agents),
|
|
433
|
+
automations: Object.freeze(automations),
|
|
434
|
+
defaultAgentId: string(root.defaultAgentId, "manifest.defaultAgentId"),
|
|
435
|
+
defaultEndpointId: string(root.defaultEndpointId, "manifest.defaultEndpointId"),
|
|
436
|
+
endpoints: Object.freeze(endpoints),
|
|
437
|
+
plugins: Object.freeze(plugins)
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
function record(value, path) {
|
|
441
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error(`${path} must be an object`);
|
|
442
|
+
return value;
|
|
443
|
+
}
|
|
444
|
+
function array(value, path) {
|
|
445
|
+
if (!Array.isArray(value)) throw new Error(`${path} must be an array`);
|
|
446
|
+
return value;
|
|
447
|
+
}
|
|
448
|
+
function string(value, path) {
|
|
449
|
+
if (typeof value !== "string" || value.trim() === "") throw new Error(`${path} must be a non-empty string`);
|
|
450
|
+
return value;
|
|
451
|
+
}
|
|
452
|
+
function boolean(value, path) {
|
|
453
|
+
if (typeof value !== "boolean") throw new Error(`${path} must be a boolean`);
|
|
454
|
+
return value;
|
|
455
|
+
}
|
|
456
|
+
function positiveInteger(value, path) {
|
|
457
|
+
if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`${path} must be a positive integer`);
|
|
458
|
+
return value;
|
|
459
|
+
}
|
|
460
|
+
function memoryScope(value, path) {
|
|
461
|
+
if (typeof value !== "string" || !MEMORY_SCOPES.includes(value)) throw new Error(`${path} must be conversation, agent-private, or shared-user-profile`);
|
|
462
|
+
return value;
|
|
463
|
+
}
|
|
464
|
+
function groupPolicy(value, path) {
|
|
465
|
+
if (value !== "mention-only" && value !== "ignore-unmentioned" && value !== "default-responder") throw new Error(`${path} must be mention-only, ignore-unmentioned, or default-responder`);
|
|
466
|
+
return value;
|
|
467
|
+
}
|
|
468
|
+
function automationTargetType(value, path) {
|
|
469
|
+
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`);
|
|
470
|
+
return value;
|
|
471
|
+
}
|
|
472
|
+
function exactKeys(value, allowed, path, optional = []) {
|
|
473
|
+
const unexpected = Object.keys(value).find((key) => !allowed.includes(key));
|
|
474
|
+
if (unexpected) throw new Error(`${path} contains unsupported field: ${unexpected}`);
|
|
475
|
+
const missing = allowed.find((key) => !optional.includes(key) && !Object.hasOwn(value, key));
|
|
476
|
+
if (missing) throw new Error(`${path} is missing required field: ${missing}`);
|
|
477
|
+
}
|
|
478
|
+
//#endregion
|
|
479
|
+
//#region src/infrastructure/plugin/node-rivus-plugin-module-loader.ts
|
|
480
|
+
async function loadNodeRivusPluginModule(request) {
|
|
481
|
+
return await import(pathToFileURL(await resolveNodeRivusPluginModulePath(request)).href);
|
|
482
|
+
}
|
|
483
|
+
async function resolveNodeRivusPluginModulePath(request) {
|
|
484
|
+
const deploymentRoot = await realpath(request.deploymentRoot);
|
|
485
|
+
const resolvedRealpath = await realpath(createRequire(join(deploymentRoot, "package.json")).resolve(request.module));
|
|
486
|
+
if (!isWithin(deploymentRoot, resolvedRealpath)) throw new Error(`plugin module ${request.module} resolves outside deployment root: ${resolvedRealpath}`);
|
|
487
|
+
return resolvedRealpath;
|
|
488
|
+
}
|
|
489
|
+
function isWithin(root, candidate) {
|
|
490
|
+
const child = relative(root, candidate);
|
|
491
|
+
return child === "" || !child.startsWith(`..${sep}`) && child !== ".." && !isAbsolute(child);
|
|
492
|
+
}
|
|
493
|
+
//#endregion
|
|
9
494
|
//#region src/infrastructure/config/rivus-daemon-config.ts
|
|
10
495
|
var RivusDaemonConfigError = class {
|
|
11
496
|
variable;
|
|
@@ -83,72 +568,16 @@ function optionalPositiveInteger(value, variable, fallback) {
|
|
|
83
568
|
if (/^[1-9]\d*$/.test(normalized)) return Effect.succeed(Number(normalized));
|
|
84
569
|
return Effect.fail(new RivusDaemonConfigError(variable, `${variable} must be a positive integer`));
|
|
85
570
|
}
|
|
86
|
-
function required(env, variable) {
|
|
87
|
-
const value = optional(env[variable]);
|
|
88
|
-
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
|
-
});
|
|
571
|
+
function required(env, variable) {
|
|
572
|
+
const value = optional(env[variable]);
|
|
573
|
+
if (value) return Effect.succeed(value);
|
|
574
|
+
return Effect.fail(new RivusDaemonConfigError(variable, `${variable} is required`));
|
|
575
|
+
}
|
|
576
|
+
function optionalThinkingLevel(value) {
|
|
577
|
+
const normalized = optional(value);
|
|
578
|
+
if (!normalized) return Effect.succeed(void 0);
|
|
579
|
+
if (THINKING_LEVELS$1.has(normalized)) return Effect.succeed(normalized);
|
|
580
|
+
return Effect.fail(new RivusDaemonConfigError("PI_THINKING_LEVEL", "PI_THINKING_LEVEL must be one of off, minimal, low, medium, high, xhigh"));
|
|
152
581
|
}
|
|
153
582
|
//#endregion
|
|
154
583
|
//#region src/infrastructure/config/openclaw-env-import.ts
|
|
@@ -294,265 +723,58 @@ function readModelId(modelReference) {
|
|
|
294
723
|
const separator = modelReference.indexOf("/");
|
|
295
724
|
return separator >= 0 && separator < modelReference.length - 1 ? modelReference.slice(separator + 1) : void 0;
|
|
296
725
|
}
|
|
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");
|
|
726
|
+
function inferFeishuBaseUrl(domain) {
|
|
727
|
+
return domain === "lark" ? DEFAULT_LARK_BASE_URL : DEFAULT_FEISHU_BASE_URL;
|
|
546
728
|
}
|
|
547
|
-
function
|
|
548
|
-
|
|
549
|
-
if ((moduleSpecifier.startsWith("./") || moduleSpecifier.startsWith("../")) && moduleSpecifier.split(/[\\/]/).includes("..")) throw new Error(`plugin module escapes deployment root: ${moduleSpecifier}`);
|
|
729
|
+
function quoteEnvValue(value) {
|
|
730
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
550
731
|
}
|
|
551
|
-
|
|
552
|
-
const
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
732
|
+
function requiredString(record, key, path) {
|
|
733
|
+
const value = optionalString(record[key]);
|
|
734
|
+
if (!value) throw new OpenClawEnvImportError(`${path} is required`);
|
|
735
|
+
return value;
|
|
736
|
+
}
|
|
737
|
+
function optionalString(value) {
|
|
738
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
739
|
+
}
|
|
740
|
+
function asRecord(value, path) {
|
|
741
|
+
const record = optionalRecord(value);
|
|
742
|
+
if (!record) throw new OpenClawEnvImportError(`${path} must be an object`);
|
|
743
|
+
return record;
|
|
744
|
+
}
|
|
745
|
+
function optionalRecord(value) {
|
|
746
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
747
|
+
}
|
|
748
|
+
function readPath(record, path) {
|
|
749
|
+
let value = record;
|
|
750
|
+
for (const segment of path) {
|
|
751
|
+
const current = optionalRecord(value);
|
|
752
|
+
if (!current) return;
|
|
753
|
+
value = current[segment];
|
|
754
|
+
}
|
|
755
|
+
return value;
|
|
756
|
+
}
|
|
757
|
+
//#endregion
|
|
758
|
+
//#region src/application/daemon/rivus-daemon-shutdown-controller.ts
|
|
759
|
+
const DEFAULT_SIGNALS = ["SIGINT", "SIGTERM"];
|
|
760
|
+
function createRivusDaemonShutdownController(options) {
|
|
761
|
+
let shutdown;
|
|
762
|
+
const handle = (signal) => {
|
|
763
|
+
shutdown ??= Effect.runPromise(options.daemon.stop()).then(() => options.onStopped?.(signal)).catch(async (error) => {
|
|
764
|
+
await options.onError?.(error, signal);
|
|
765
|
+
throw error;
|
|
766
|
+
});
|
|
767
|
+
return shutdown;
|
|
768
|
+
};
|
|
769
|
+
return {
|
|
770
|
+
handle,
|
|
771
|
+
install: () => {
|
|
772
|
+
for (const signal of options.signals ?? DEFAULT_SIGNALS) options.signalSource.on(signal, () => {
|
|
773
|
+
handle(signal);
|
|
774
|
+
});
|
|
775
|
+
},
|
|
776
|
+
stopping: () => shutdown !== void 0
|
|
777
|
+
};
|
|
556
778
|
}
|
|
557
779
|
//#endregion
|
|
558
780
|
//#region src/application/support/stable-id.ts
|
|
@@ -781,10 +1003,11 @@ async function createRivusDeploymentDaemon(options) {
|
|
|
781
1003
|
const slotById = new Map(slots.map((slot) => [slot.definition.id, slot]));
|
|
782
1004
|
let lifecycle = "stopped";
|
|
783
1005
|
const canRunIntake = () => lifecycle === "running" || lifecycle === "degraded";
|
|
1006
|
+
const canRunEndpointIntake = (slot) => (canRunIntake() || lifecycle === "starting") && (slot.lifecycle === "running" || slot.lifecycle === "starting") && (slot.adapter?.running() ?? false);
|
|
784
1007
|
const handleEndpoint = async (endpointId, input) => {
|
|
785
1008
|
const slot = slotById.get(endpointId);
|
|
786
1009
|
if (!slot) throw new RivusDeploymentDaemonLifecycleError(`unknown endpoint: ${endpointId}`);
|
|
787
|
-
if (!
|
|
1010
|
+
if (!canRunEndpointIntake(slot)) throw new RivusDeploymentDaemonLifecycleError(`endpoint ${endpointId} cannot accept intake while ${slot.lifecycle}`);
|
|
788
1011
|
return host.handleEndpoint(endpointId, input);
|
|
789
1012
|
};
|
|
790
1013
|
const endpointStatus = (slot) => Object.freeze({
|
|
@@ -846,13 +1069,15 @@ async function createRivusDeploymentDaemon(options) {
|
|
|
846
1069
|
}
|
|
847
1070
|
for (const slot of automationSlots) {
|
|
848
1071
|
const resolvedDefinition = slot.resolvedDefinition;
|
|
849
|
-
const
|
|
1072
|
+
const deliverySlot = slotById.get(slot.definition.delivery.endpointId);
|
|
1073
|
+
const presentationReady = deliverySlot.lifecycle === "running" && (deliverySlot.adapter?.running() ?? false);
|
|
1074
|
+
const slotDegraded = await startSlot(slot, slot.agentEnabled && resolvedDefinition !== void 0 && presentationReady, async () => {
|
|
850
1075
|
if (!options.createAutomation) throw new RivusDeploymentDaemonLifecycleError("deployment bootstrap does not provide Automation adapters");
|
|
851
1076
|
if (!resolvedDefinition) throw new RivusDeploymentDaemonLifecycleError(`automation definition not resolved: ${slot.definition.id}`);
|
|
852
1077
|
return options.createAutomation({
|
|
853
1078
|
automationId: slot.definition.id,
|
|
854
1079
|
definition: resolvedDefinition,
|
|
855
|
-
deliveryEndpoint:
|
|
1080
|
+
deliveryEndpoint: deliverySlot.definition,
|
|
856
1081
|
instanceId: host.resolveAutomation(slot.definition.id).instanceId,
|
|
857
1082
|
run: (input) => host.handleAutomation(slot.definition.id, {
|
|
858
1083
|
invocation: {
|
|
@@ -946,204 +1171,6 @@ async function stopSlots(slots, errors) {
|
|
|
946
1171
|
}
|
|
947
1172
|
}
|
|
948
1173
|
//#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
1174
|
//#region src/infrastructure/runtime/configured-rivus-deployment-daemon.ts
|
|
1148
1175
|
async function createConfiguredRivusDeploymentDaemon(options) {
|
|
1149
1176
|
const manifest = await loadRivusDeploymentManifest(options.manifestPath, options.manifestOptions);
|
|
@@ -1452,6 +1479,10 @@ const USAGE = `Usage: rivus --bootstrap <module> [--manifest <rivus.config.json>
|
|
|
1452
1479
|
|
|
1453
1480
|
Starts a local Rivus Agent daemon from an injected bootstrap module.
|
|
1454
1481
|
|
|
1482
|
+
Project commands:
|
|
1483
|
+
rivus init [directory]
|
|
1484
|
+
rivus doctor [directory] [--env-file <path>]
|
|
1485
|
+
|
|
1455
1486
|
Options:
|
|
1456
1487
|
--bootstrap <module> Module exporting createRivusDaemonProcess(context)
|
|
1457
1488
|
--manifest <path> Start the manifest-driven multi-agent deployment; bootstrap exports createRivusDeploymentAdapters(context)
|
|
@@ -2596,4 +2627,4 @@ function hasRecoveryRunner(daemon) {
|
|
|
2596
2627
|
return typeof daemon.openRecoveryControl === "function";
|
|
2597
2628
|
}
|
|
2598
2629
|
//#endregion
|
|
2599
|
-
export {
|
|
2630
|
+
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 };
|