@rivus/agent 0.1.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/LICENSE +22 -0
- package/README.md +1051 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +22 -0
- package/dist/index.d.ts +3423 -0
- package/dist/index.js +7337 -0
- package/dist/rivus-daemon-cli.js +2599 -0
- package/dist/rivus-plugin-registry.js +316 -0
- package/dist/rivus-plugin-testkit.d.ts +291 -0
- package/dist/rivus-plugin-testkit.js +62 -0
- package/dist/testing/index.d.ts +55 -0
- package/dist/testing/index.js +94 -0
- package/examples/current-weather.mjs +143 -0
- package/examples/html-drive-tools.mjs +262 -0
- package/examples/langfuse-drive-e2e.mjs +175 -0
- package/examples/pi-feishu-deployment.bootstrap.ts +542 -0
- package/examples/pi-feishu.bootstrap.ts +225 -0
- package/examples/rivus-agents.plugin.mjs +238 -0
- package/examples/rivus-langfuse-demo.config.json +36 -0
- package/examples/rivus.config.json +83 -0
- package/package.json +112 -0
|
@@ -0,0 +1,2599 @@
|
|
|
1
|
+
import { i as MEMORY_SCOPES, n as resolveRivusAgentDefinition, r as deepFreeze, t as createRivusPluginCatalog } from "./rivus-plugin-registry.js";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { dirname, isAbsolute, join, relative, sep } from "node:path";
|
|
5
|
+
import { Effect } from "effect";
|
|
6
|
+
import { open, readFile, realpath, stat } from "node:fs/promises";
|
|
7
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
8
|
+
import { constants } from "node:fs";
|
|
9
|
+
//#region src/infrastructure/config/rivus-daemon-config.ts
|
|
10
|
+
var RivusDaemonConfigError = class {
|
|
11
|
+
variable;
|
|
12
|
+
message;
|
|
13
|
+
_tag = "RivusDaemonConfigError";
|
|
14
|
+
constructor(variable, message) {
|
|
15
|
+
this.variable = variable;
|
|
16
|
+
this.message = message;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
const DEFAULT_AGENT_ID$1 = "main";
|
|
20
|
+
const DEFAULT_FEISHU_BASE_URL$1 = "https://open.feishu.cn";
|
|
21
|
+
const DEFAULT_STREAM_MIN_INTERVAL_MS$1 = 200;
|
|
22
|
+
const THINKING_LEVELS$1 = /* @__PURE__ */ new Set([
|
|
23
|
+
"off",
|
|
24
|
+
"minimal",
|
|
25
|
+
"low",
|
|
26
|
+
"medium",
|
|
27
|
+
"high",
|
|
28
|
+
"xhigh"
|
|
29
|
+
]);
|
|
30
|
+
function loadRivusDaemonConfig(env, options = {}) {
|
|
31
|
+
return Effect.gen(function* () {
|
|
32
|
+
const appId = yield* required(env, "FEISHU_APP_ID");
|
|
33
|
+
const appSecret = yield* required(env, "FEISHU_APP_SECRET");
|
|
34
|
+
const streamMinIntervalMs = yield* optionalPositiveInteger(env.FEISHU_STREAM_MIN_INTERVAL_MS, "FEISHU_STREAM_MIN_INTERVAL_MS", DEFAULT_STREAM_MIN_INTERVAL_MS$1);
|
|
35
|
+
const thinkingLevel = yield* optionalThinkingLevel(env.PI_THINKING_LEVEL);
|
|
36
|
+
const apiKey = yield* optionalPiApiKey(env, options.readTextFile ?? readUtf8File);
|
|
37
|
+
const baseUrl = optional(env.PI_BASE_URL);
|
|
38
|
+
const model = optional(env.PI_MODEL);
|
|
39
|
+
return {
|
|
40
|
+
agentId: optional(env.RIVUS_AGENT_ID) ?? DEFAULT_AGENT_ID$1,
|
|
41
|
+
feishu: {
|
|
42
|
+
appId,
|
|
43
|
+
appSecret,
|
|
44
|
+
baseUrl: optional(env.FEISHU_BASE_URL) ?? DEFAULT_FEISHU_BASE_URL$1,
|
|
45
|
+
streamMinIntervalMs
|
|
46
|
+
},
|
|
47
|
+
pi: {
|
|
48
|
+
...apiKey ? { apiKey } : {},
|
|
49
|
+
...baseUrl ? { baseUrl } : {},
|
|
50
|
+
...model ? { model } : {},
|
|
51
|
+
...thinkingLevel ? { thinkingLevel } : {}
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function optional(value) {
|
|
57
|
+
const trimmed = value?.trim();
|
|
58
|
+
return trimmed ? trimmed : void 0;
|
|
59
|
+
}
|
|
60
|
+
function readUtf8File(path) {
|
|
61
|
+
return readFile(path, "utf8");
|
|
62
|
+
}
|
|
63
|
+
function optionalPiApiKey(env, readTextFile) {
|
|
64
|
+
const inlineApiKey = optional(env.PI_API_KEY);
|
|
65
|
+
const apiKeyFile = optional(env.PI_API_KEY_FILE);
|
|
66
|
+
if (inlineApiKey && apiKeyFile) return Effect.fail(new RivusDaemonConfigError("PI_API_KEY", "PI_API_KEY and PI_API_KEY_FILE cannot both be set"));
|
|
67
|
+
if (inlineApiKey) return Effect.succeed(inlineApiKey);
|
|
68
|
+
if (!apiKeyFile) return Effect.succeed(void 0);
|
|
69
|
+
return Effect.tryPromise({
|
|
70
|
+
try: async () => readTextFile(apiKeyFile),
|
|
71
|
+
catch: (error) => new RivusDaemonConfigError("PI_API_KEY_FILE", `PI_API_KEY_FILE could not be read: ${formatConfigError(error)}`)
|
|
72
|
+
}).pipe(Effect.flatMap((contents) => {
|
|
73
|
+
const apiKey = optional(contents);
|
|
74
|
+
return apiKey ? Effect.succeed(apiKey) : Effect.fail(new RivusDaemonConfigError("PI_API_KEY_FILE", "PI_API_KEY_FILE must not be empty"));
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
function formatConfigError(error) {
|
|
78
|
+
return error instanceof Error ? error.message : String(error);
|
|
79
|
+
}
|
|
80
|
+
function optionalPositiveInteger(value, variable, fallback) {
|
|
81
|
+
const normalized = optional(value);
|
|
82
|
+
if (!normalized) return Effect.succeed(fallback);
|
|
83
|
+
if (/^[1-9]\d*$/.test(normalized)) return Effect.succeed(Number(normalized));
|
|
84
|
+
return Effect.fail(new RivusDaemonConfigError(variable, `${variable} must be a positive integer`));
|
|
85
|
+
}
|
|
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
|
+
});
|
|
152
|
+
}
|
|
153
|
+
//#endregion
|
|
154
|
+
//#region src/infrastructure/config/openclaw-env-import.ts
|
|
155
|
+
var OpenClawEnvImportError = class extends Error {
|
|
156
|
+
constructor(message) {
|
|
157
|
+
super(message);
|
|
158
|
+
this.name = "OpenClawEnvImportError";
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
const DEFAULT_AGENT_ID = "main";
|
|
162
|
+
const DEFAULT_FEISHU_BASE_URL = "https://open.feishu.cn";
|
|
163
|
+
const DEFAULT_LARK_BASE_URL = "https://open.larksuite.com";
|
|
164
|
+
const DEFAULT_STREAM_MIN_INTERVAL_MS = 200;
|
|
165
|
+
const THINKING_LEVELS = /* @__PURE__ */ new Set([
|
|
166
|
+
"off",
|
|
167
|
+
"minimal",
|
|
168
|
+
"low",
|
|
169
|
+
"medium",
|
|
170
|
+
"high",
|
|
171
|
+
"xhigh"
|
|
172
|
+
]);
|
|
173
|
+
const ENV_FILE_ORDER = [
|
|
174
|
+
"FEISHU_APP_ID",
|
|
175
|
+
"FEISHU_APP_SECRET",
|
|
176
|
+
"FEISHU_BASE_URL",
|
|
177
|
+
"FEISHU_STREAM_MIN_INTERVAL_MS",
|
|
178
|
+
"RIVUS_AGENT_ID",
|
|
179
|
+
"PI_API_KEY_FILE",
|
|
180
|
+
"PI_BASE_URL",
|
|
181
|
+
"PI_MODEL",
|
|
182
|
+
"PI_THINKING_LEVEL"
|
|
183
|
+
];
|
|
184
|
+
function createRivusEnvFromOpenClawConfig(openClawConfig, options = {}) {
|
|
185
|
+
const config = asRecord(openClawConfig, "OpenClaw config");
|
|
186
|
+
const feishu = asRecord(readPath(config, ["channels", "feishu"]), "channels.feishu");
|
|
187
|
+
const appId = requiredString(feishu, "appId", "channels.feishu.appId");
|
|
188
|
+
const appSecret = requiredString(feishu, "appSecret", "channels.feishu.appSecret");
|
|
189
|
+
const modelReference = findPrimaryModelReference(config);
|
|
190
|
+
const providerId = modelReference ? parseProviderId(modelReference) : void 0;
|
|
191
|
+
const provider = providerId ? optionalRecord(readPath(config, [
|
|
192
|
+
"models",
|
|
193
|
+
"providers",
|
|
194
|
+
providerId
|
|
195
|
+
])) : void 0;
|
|
196
|
+
const providerBaseUrl = provider ? optionalString(provider.baseUrl) : void 0;
|
|
197
|
+
const thinkingLevel = modelReference ? findThinkingLevel(config, modelReference) : void 0;
|
|
198
|
+
const warnings = thinkingLevel?.warning ? [thinkingLevel.warning] : [];
|
|
199
|
+
return {
|
|
200
|
+
env: {
|
|
201
|
+
FEISHU_APP_ID: appId,
|
|
202
|
+
FEISHU_APP_SECRET: appSecret,
|
|
203
|
+
FEISHU_BASE_URL: options.feishuBaseUrl ?? inferFeishuBaseUrl(optionalString(feishu.domain)),
|
|
204
|
+
FEISHU_STREAM_MIN_INTERVAL_MS: String(options.streamMinIntervalMs ?? DEFAULT_STREAM_MIN_INTERVAL_MS),
|
|
205
|
+
RIVUS_AGENT_ID: findAgentId(config),
|
|
206
|
+
...options.piApiKeyFile ? { PI_API_KEY_FILE: options.piApiKeyFile } : {},
|
|
207
|
+
...providerBaseUrl ? { PI_BASE_URL: providerBaseUrl } : {},
|
|
208
|
+
...modelReference ? { PI_MODEL: modelReference } : {},
|
|
209
|
+
...thinkingLevel?.level ? { PI_THINKING_LEVEL: thinkingLevel.level } : {}
|
|
210
|
+
},
|
|
211
|
+
warnings
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function formatRivusEnvFile(env) {
|
|
215
|
+
return `${[...ENV_FILE_ORDER.filter((key) => Object.hasOwn(env, key)), ...Object.keys(env).filter((key) => !ENV_FILE_ORDER.includes(key)).sort()].map((key) => `${key}=${quoteEnvValue(env[key] ?? "")}`).join("\n")}\n`;
|
|
216
|
+
}
|
|
217
|
+
function findAgentId(config) {
|
|
218
|
+
const agents = optionalRecord(config.agents);
|
|
219
|
+
return optionalString(optionalRecord((Array.isArray(agents?.list) ? agents.list : [])[0])?.id) ?? DEFAULT_AGENT_ID;
|
|
220
|
+
}
|
|
221
|
+
function findPrimaryModelReference(config) {
|
|
222
|
+
const primary = optionalString(readPath(config, [
|
|
223
|
+
"agents",
|
|
224
|
+
"defaults",
|
|
225
|
+
"model",
|
|
226
|
+
"primary"
|
|
227
|
+
]));
|
|
228
|
+
if (primary) return primary;
|
|
229
|
+
const providers = optionalRecord(readPath(config, ["models", "providers"]));
|
|
230
|
+
if (!providers) return;
|
|
231
|
+
for (const [providerId, provider] of Object.entries(providers)) {
|
|
232
|
+
const model = firstModelId(optionalRecord(provider));
|
|
233
|
+
if (model) return `${providerId}/${model}`;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function firstModelId(provider) {
|
|
237
|
+
return optionalString(optionalRecord((Array.isArray(provider?.models) ? provider.models : [])[0])?.id);
|
|
238
|
+
}
|
|
239
|
+
function parseProviderId(modelReference) {
|
|
240
|
+
const separator = modelReference.indexOf("/");
|
|
241
|
+
return separator > 0 ? modelReference.slice(0, separator) : void 0;
|
|
242
|
+
}
|
|
243
|
+
function findThinkingLevel(config, modelReference) {
|
|
244
|
+
const providerId = parseProviderId(modelReference);
|
|
245
|
+
const modelId = readModelId(modelReference);
|
|
246
|
+
const rawLevel = [
|
|
247
|
+
readPath(config, [
|
|
248
|
+
"agents",
|
|
249
|
+
"defaults",
|
|
250
|
+
"models",
|
|
251
|
+
modelReference,
|
|
252
|
+
"thinkingLevel"
|
|
253
|
+
]),
|
|
254
|
+
readPath(config, [
|
|
255
|
+
"agents",
|
|
256
|
+
"defaults",
|
|
257
|
+
"models",
|
|
258
|
+
modelReference,
|
|
259
|
+
"thinkLevel"
|
|
260
|
+
]),
|
|
261
|
+
readPath(config, [
|
|
262
|
+
"agents",
|
|
263
|
+
"defaults",
|
|
264
|
+
"models",
|
|
265
|
+
modelReference,
|
|
266
|
+
"reasoningLevel"
|
|
267
|
+
]),
|
|
268
|
+
...providerId && modelId ? readProviderModelThinkingCandidates(config, providerId, modelId) : []
|
|
269
|
+
].map(optionalString).find(Boolean);
|
|
270
|
+
if (!rawLevel) return;
|
|
271
|
+
if (THINKING_LEVELS.has(rawLevel)) return { level: rawLevel };
|
|
272
|
+
return { warning: `Unsupported OpenClaw thinking level '${rawLevel}' was ignored` };
|
|
273
|
+
}
|
|
274
|
+
function readProviderModelThinkingCandidates(config, providerId, modelId) {
|
|
275
|
+
const providerModels = readPath(config, [
|
|
276
|
+
"models",
|
|
277
|
+
"providers",
|
|
278
|
+
providerId,
|
|
279
|
+
"models"
|
|
280
|
+
]);
|
|
281
|
+
if (!Array.isArray(providerModels)) return [];
|
|
282
|
+
const model = providerModels.map(optionalRecord).find((candidate) => optionalString(candidate?.id) === modelId);
|
|
283
|
+
if (!model) return [];
|
|
284
|
+
return [
|
|
285
|
+
model.thinkingLevel,
|
|
286
|
+
model.thinkLevel,
|
|
287
|
+
model.reasoningLevel,
|
|
288
|
+
readPath(model, ["reasoning", "level"]),
|
|
289
|
+
readPath(model, ["reasoning", "thinkingLevel"]),
|
|
290
|
+
readPath(model, ["reasoning", "thinkLevel"])
|
|
291
|
+
];
|
|
292
|
+
}
|
|
293
|
+
function readModelId(modelReference) {
|
|
294
|
+
const separator = modelReference.indexOf("/");
|
|
295
|
+
return separator >= 0 && separator < modelReference.length - 1 ? modelReference.slice(separator + 1) : void 0;
|
|
296
|
+
}
|
|
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");
|
|
546
|
+
}
|
|
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}`);
|
|
550
|
+
}
|
|
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;
|
|
556
|
+
}
|
|
557
|
+
//#endregion
|
|
558
|
+
//#region src/application/support/stable-id.ts
|
|
559
|
+
function createStableId(prefix, value) {
|
|
560
|
+
return `${prefix}:${createHash("sha256").update(JSON.stringify(value)).digest("hex")}`;
|
|
561
|
+
}
|
|
562
|
+
//#endregion
|
|
563
|
+
//#region src/application/host/agent-instance-registry.ts
|
|
564
|
+
var AgentInstanceConflict = class extends Error {
|
|
565
|
+
name = "AgentInstanceConflict";
|
|
566
|
+
};
|
|
567
|
+
function createAgentInstanceRegistry(options = {}) {
|
|
568
|
+
const records = /* @__PURE__ */ new Map();
|
|
569
|
+
for (const record of options.initialRecords ?? []) {
|
|
570
|
+
if (records.has(record.bindingKey)) throw new AgentInstanceConflict(`duplicate binding: ${record.bindingKey}`);
|
|
571
|
+
records.set(record.bindingKey, Object.freeze({ ...record }));
|
|
572
|
+
}
|
|
573
|
+
const resolveBinding = (binding, definition) => {
|
|
574
|
+
const runtimeGenerationId = createStableId("generation", {
|
|
575
|
+
agentId: definition.agentId,
|
|
576
|
+
profileRevision: definition.profileRevision,
|
|
577
|
+
toolGrantRevision: definition.toolGrantSet.revision
|
|
578
|
+
});
|
|
579
|
+
const bindingId = binding.kind === "endpoint" ? binding.endpointId : binding.automationId;
|
|
580
|
+
const bindingKey = `${binding.kind}:${bindingId}:${definition.agentId}`;
|
|
581
|
+
const existing = records.get(bindingKey);
|
|
582
|
+
if (existing) {
|
|
583
|
+
if (existing.runtimeGenerationId !== runtimeGenerationId) throw new AgentInstanceConflict(`binding ${bindingKey} belongs to a different runtime generation`);
|
|
584
|
+
return existing;
|
|
585
|
+
}
|
|
586
|
+
const record = Object.freeze({
|
|
587
|
+
agentId: definition.agentId,
|
|
588
|
+
binding: Object.freeze({ ...binding }),
|
|
589
|
+
bindingKey,
|
|
590
|
+
instanceId: createStableId("instance", {
|
|
591
|
+
bindingKey,
|
|
592
|
+
runtimeGenerationId
|
|
593
|
+
}),
|
|
594
|
+
runtimeGenerationId
|
|
595
|
+
});
|
|
596
|
+
records.set(bindingKey, record);
|
|
597
|
+
return record;
|
|
598
|
+
};
|
|
599
|
+
return {
|
|
600
|
+
resolveAutomation: (automationId, definition) => resolveBinding({
|
|
601
|
+
automationId,
|
|
602
|
+
kind: "automation"
|
|
603
|
+
}, definition),
|
|
604
|
+
resolveEndpoint: (endpointId, definition) => resolveBinding({
|
|
605
|
+
endpointId,
|
|
606
|
+
kind: "endpoint"
|
|
607
|
+
}, definition),
|
|
608
|
+
snapshot: () => Object.freeze([...records.values()])
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
//#endregion
|
|
612
|
+
//#region src/application/host/agent-runtime-pool.ts
|
|
613
|
+
var AgentInstanceBusy = class extends Error {
|
|
614
|
+
name = "AgentInstanceBusy";
|
|
615
|
+
};
|
|
616
|
+
var AgentRuntimeDisposed = class extends Error {
|
|
617
|
+
name = "AgentRuntimeDisposed";
|
|
618
|
+
};
|
|
619
|
+
const DEFAULT_RUNTIME_DISPOSE_TIMEOUT_MS = 1e4;
|
|
620
|
+
function createAgentRuntimePool(options) {
|
|
621
|
+
const runtimes = /* @__PURE__ */ new Map();
|
|
622
|
+
const active = /* @__PURE__ */ new Map();
|
|
623
|
+
const resolveRuntime = (instance) => {
|
|
624
|
+
const current = runtimes.get(instance.instanceId);
|
|
625
|
+
if (current) return current;
|
|
626
|
+
let created;
|
|
627
|
+
created = Promise.resolve(options.createRuntime(instance)).catch((error) => {
|
|
628
|
+
if (runtimes.get(instance.instanceId) === created) runtimes.delete(instance.instanceId);
|
|
629
|
+
throw error;
|
|
630
|
+
});
|
|
631
|
+
runtimes.set(instance.instanceId, created);
|
|
632
|
+
return created;
|
|
633
|
+
};
|
|
634
|
+
return {
|
|
635
|
+
registry: options.registry,
|
|
636
|
+
cancel: async (instance, input) => {
|
|
637
|
+
const runtime = runtimes.get(instance.instanceId);
|
|
638
|
+
return runtime ? (await runtime).cancel?.(input) ?? false : false;
|
|
639
|
+
},
|
|
640
|
+
disposeAll: async () => {
|
|
641
|
+
const pendingRuntimes = [...runtimes.values()];
|
|
642
|
+
runtimes.clear();
|
|
643
|
+
active.clear();
|
|
644
|
+
await withTimeout(Promise.all(pendingRuntimes.map(async (runtime) => {
|
|
645
|
+
await (await runtime).dispose?.();
|
|
646
|
+
})), options.disposeTimeoutMs ?? DEFAULT_RUNTIME_DISPOSE_TIMEOUT_MS, "Agent runtime disposal timed out");
|
|
647
|
+
},
|
|
648
|
+
run: async (instance, input) => {
|
|
649
|
+
const pendingRuntime = resolveRuntime(instance);
|
|
650
|
+
const runtime = await pendingRuntime;
|
|
651
|
+
if (runtimes.get(instance.instanceId) !== pendingRuntime) throw new AgentRuntimeDisposed(`agent runtime was disposed before run start: ${instance.instanceId}`);
|
|
652
|
+
if (runtime.concurrency === "managed") return runtime.run(input);
|
|
653
|
+
if (active.has(instance.instanceId)) throw new AgentInstanceBusy(`agent instance already has an active run: ${instance.instanceId}`);
|
|
654
|
+
const activeToken = Symbol(instance.instanceId);
|
|
655
|
+
active.set(instance.instanceId, activeToken);
|
|
656
|
+
try {
|
|
657
|
+
return await runtime.run(input);
|
|
658
|
+
} finally {
|
|
659
|
+
if (active.get(instance.instanceId) === activeToken) active.delete(instance.instanceId);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
async function withTimeout(promise, timeoutMs, message) {
|
|
665
|
+
let timeout;
|
|
666
|
+
try {
|
|
667
|
+
return await Promise.race([promise, new Promise((_resolve, reject) => {
|
|
668
|
+
timeout = setTimeout(() => reject(/* @__PURE__ */ new Error(`${message} after ${timeoutMs}ms`)), timeoutMs);
|
|
669
|
+
})]);
|
|
670
|
+
} finally {
|
|
671
|
+
if (timeout) clearTimeout(timeout);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
//#endregion
|
|
675
|
+
//#region src/application/host/rivus-agent-host.ts
|
|
676
|
+
var InvalidRivusEndpointBinding = class extends Error {
|
|
677
|
+
name = "InvalidRivusEndpointBinding";
|
|
678
|
+
};
|
|
679
|
+
function createRivusAgentHost(options) {
|
|
680
|
+
const definitions = new Map(options.definitions.map((definition) => [definition.agentId, definition]));
|
|
681
|
+
const endpoints = /* @__PURE__ */ new Map();
|
|
682
|
+
const automations = /* @__PURE__ */ new Map();
|
|
683
|
+
for (const endpoint of options.endpoints) {
|
|
684
|
+
if (endpoints.has(endpoint.id)) throw new InvalidRivusEndpointBinding(`duplicate endpoint: ${endpoint.id}`);
|
|
685
|
+
const definition = definitions.get(endpoint.agentId);
|
|
686
|
+
if (!definition) throw new InvalidRivusEndpointBinding(`unknown endpoint agent: ${endpoint.agentId}`);
|
|
687
|
+
if (!definition.endpointIds.includes(endpoint.id)) throw new InvalidRivusEndpointBinding(`endpoint ${endpoint.id} is not declared by ${endpoint.agentId}`);
|
|
688
|
+
endpoints.set(endpoint.id, options.runtimePool.registry.resolveEndpoint(endpoint.id, definition));
|
|
689
|
+
}
|
|
690
|
+
for (const automation of options.automations ?? []) {
|
|
691
|
+
if (automations.has(automation.id)) throw new InvalidRivusEndpointBinding(`duplicate automation: ${automation.id}`);
|
|
692
|
+
if (!definitions.has(automation.definition.agentId)) throw new InvalidRivusEndpointBinding(`unknown automation agent: ${automation.definition.agentId}`);
|
|
693
|
+
automations.set(automation.id, options.runtimePool.registry.resolveAutomation(automation.id, automation.definition));
|
|
694
|
+
}
|
|
695
|
+
const resolveEndpoint = (endpointId) => {
|
|
696
|
+
const instance = endpoints.get(endpointId);
|
|
697
|
+
if (!instance) throw new InvalidRivusEndpointBinding(`unknown endpoint: ${endpointId}`);
|
|
698
|
+
return instance;
|
|
699
|
+
};
|
|
700
|
+
const resolveAutomation = (automationId) => {
|
|
701
|
+
const instance = automations.get(automationId);
|
|
702
|
+
if (!instance) throw new InvalidRivusEndpointBinding(`unknown automation: ${automationId}`);
|
|
703
|
+
return instance;
|
|
704
|
+
};
|
|
705
|
+
return {
|
|
706
|
+
cancelEndpoint: (endpointId, input) => options.runtimePool.cancel(resolveEndpoint(endpointId), input),
|
|
707
|
+
handleAutomation: (automationId, input) => options.runtimePool.run(resolveAutomation(automationId), input),
|
|
708
|
+
handleEndpoint: (endpointId, input) => options.runtimePool.run(resolveEndpoint(endpointId), input),
|
|
709
|
+
resolveAutomation,
|
|
710
|
+
resolveEndpoint
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
//#endregion
|
|
714
|
+
//#region src/application/deployment/rivus-deployment-daemon.ts
|
|
715
|
+
var RivusDeploymentDaemonLifecycleError = class extends Error {
|
|
716
|
+
name = "RivusDeploymentDaemonLifecycleError";
|
|
717
|
+
};
|
|
718
|
+
var RivusDeploymentReadinessError = class extends Error {
|
|
719
|
+
endpointIds;
|
|
720
|
+
name = "RivusDeploymentReadinessError";
|
|
721
|
+
constructor(endpointIds) {
|
|
722
|
+
super(`required endpoint startup failed: ${endpointIds.join(", ")}`);
|
|
723
|
+
this.endpointIds = endpointIds;
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
var RivusDeploymentAutomationReadinessError = class extends Error {
|
|
727
|
+
automationIds;
|
|
728
|
+
name = "RivusDeploymentAutomationReadinessError";
|
|
729
|
+
constructor(automationIds) {
|
|
730
|
+
super(`required automation startup failed: ${automationIds.join(", ")}`);
|
|
731
|
+
this.automationIds = automationIds;
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
async function createRivusDeploymentDaemon(options) {
|
|
735
|
+
const deployment = await loadRivusDeployment(options);
|
|
736
|
+
const definitions = new Map(deployment.definitions.map((definition) => [definition.agentId, definition]));
|
|
737
|
+
const automationDefinitions = new Map(deployment.automationDefinitions.map((definition) => [definition.id, definition]));
|
|
738
|
+
const runtimePool = createAgentRuntimePool({
|
|
739
|
+
createRuntime: (instance) => {
|
|
740
|
+
const definition = instance.binding.kind === "automation" ? automationDefinitions.get(instance.binding.automationId)?.runtimeDefinition : definitions.get(instance.agentId);
|
|
741
|
+
if (!definition) throw new RivusDeploymentDaemonLifecycleError(`runtime instance references unknown agent: ${instance.agentId}`);
|
|
742
|
+
return options.createRuntime({
|
|
743
|
+
...instance,
|
|
744
|
+
catalog: deployment.catalog,
|
|
745
|
+
definition
|
|
746
|
+
});
|
|
747
|
+
},
|
|
748
|
+
registry: createAgentInstanceRegistry(options.initialInstanceRecords ? { initialRecords: options.initialInstanceRecords } : {})
|
|
749
|
+
});
|
|
750
|
+
const agentStatuses = new Map(deployment.agents.map((agent) => [agent.agentId, agent]));
|
|
751
|
+
const slots = deployment.manifest.endpoints.map((definition) => {
|
|
752
|
+
const agentEnabled = agentStatuses.get(definition.agentId)?.status === "enabled";
|
|
753
|
+
return {
|
|
754
|
+
agentEnabled,
|
|
755
|
+
definition,
|
|
756
|
+
lifecycle: definition.enabled && agentEnabled ? "stopped" : "disabled"
|
|
757
|
+
};
|
|
758
|
+
});
|
|
759
|
+
const automationSlots = (deployment.manifest.automations ?? []).map((definition) => {
|
|
760
|
+
const agentEnabled = agentStatuses.get(definition.agentId)?.status === "enabled";
|
|
761
|
+
const resolvedDefinition = automationDefinitions.get(definition.id);
|
|
762
|
+
return {
|
|
763
|
+
agentEnabled,
|
|
764
|
+
definition,
|
|
765
|
+
...resolvedDefinition ? { resolvedDefinition } : {},
|
|
766
|
+
lifecycle: definition.enabled && agentEnabled ? "stopped" : "disabled"
|
|
767
|
+
};
|
|
768
|
+
});
|
|
769
|
+
const host = createRivusAgentHost({
|
|
770
|
+
automations: automationSlots.filter((slot) => slot.definition.enabled && slot.agentEnabled && slot.resolvedDefinition !== void 0).map((slot) => ({
|
|
771
|
+
definition: slot.resolvedDefinition.runtimeDefinition,
|
|
772
|
+
id: slot.definition.id
|
|
773
|
+
})),
|
|
774
|
+
definitions: deployment.definitions,
|
|
775
|
+
endpoints: slots.filter((slot) => slot.definition.enabled && slot.agentEnabled).map((slot) => ({
|
|
776
|
+
agentId: slot.definition.agentId,
|
|
777
|
+
id: slot.definition.id
|
|
778
|
+
})),
|
|
779
|
+
runtimePool
|
|
780
|
+
});
|
|
781
|
+
const slotById = new Map(slots.map((slot) => [slot.definition.id, slot]));
|
|
782
|
+
let lifecycle = "stopped";
|
|
783
|
+
const canRunIntake = () => lifecycle === "running" || lifecycle === "degraded";
|
|
784
|
+
const handleEndpoint = async (endpointId, input) => {
|
|
785
|
+
const slot = slotById.get(endpointId);
|
|
786
|
+
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}`);
|
|
788
|
+
return host.handleEndpoint(endpointId, input);
|
|
789
|
+
};
|
|
790
|
+
const endpointStatus = (slot) => Object.freeze({
|
|
791
|
+
...componentStatus(slot),
|
|
792
|
+
agentId: slot.definition.agentId,
|
|
793
|
+
endpointId: slot.definition.id
|
|
794
|
+
});
|
|
795
|
+
const automationStatus = (slot) => Object.freeze({
|
|
796
|
+
...componentStatus(slot),
|
|
797
|
+
agentId: slot.definition.agentId,
|
|
798
|
+
automationId: slot.definition.id
|
|
799
|
+
});
|
|
800
|
+
const allSlots = [...slots, ...automationSlots];
|
|
801
|
+
const isReady = () => allSlots.every(isSlotReady);
|
|
802
|
+
const failedRequiredEndpointIds = () => slots.filter((slot) => slot.definition.enabled && slot.definition.required && slot.lifecycle !== "running").map((slot) => slot.definition.id);
|
|
803
|
+
const failedRequiredAutomationIds = () => automationSlots.filter((slot) => slot.definition.enabled && slot.definition.required && slot.lifecycle !== "running").map((slot) => slot.definition.id);
|
|
804
|
+
const assertRequiredReadiness = () => {
|
|
805
|
+
const endpointIds = failedRequiredEndpointIds();
|
|
806
|
+
if (endpointIds.length > 0) throw new RivusDeploymentReadinessError(Object.freeze(endpointIds));
|
|
807
|
+
const automationIds = failedRequiredAutomationIds();
|
|
808
|
+
if (automationIds.length > 0) throw new RivusDeploymentAutomationReadinessError(Object.freeze(automationIds));
|
|
809
|
+
};
|
|
810
|
+
const status = () => Object.freeze({
|
|
811
|
+
agents: deployment.agents,
|
|
812
|
+
automations: Object.freeze(automationSlots.map(automationStatus)),
|
|
813
|
+
defaultAgentId: deployment.manifest.defaultAgentId,
|
|
814
|
+
defaultEndpointId: deployment.manifest.defaultEndpointId,
|
|
815
|
+
endpoints: Object.freeze(slots.map(endpointStatus)),
|
|
816
|
+
lifecycle,
|
|
817
|
+
plugins: deployment.plugins,
|
|
818
|
+
ready: isReady(),
|
|
819
|
+
running: canRunIntake()
|
|
820
|
+
});
|
|
821
|
+
return {
|
|
822
|
+
deployment,
|
|
823
|
+
handleDefault: (input) => handleEndpoint(deployment.manifest.defaultEndpointId, input),
|
|
824
|
+
handleEndpoint,
|
|
825
|
+
runDefaultAgent: (input) => host.handleEndpoint(deployment.manifest.defaultEndpointId, input),
|
|
826
|
+
running: canRunIntake,
|
|
827
|
+
start: async () => {
|
|
828
|
+
if (lifecycle === "running") return;
|
|
829
|
+
if (lifecycle === "degraded") {
|
|
830
|
+
assertRequiredReadiness();
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
if (lifecycle !== "stopped") throw new RivusDeploymentDaemonLifecycleError(`cannot start deployment daemon while ${lifecycle}`);
|
|
834
|
+
lifecycle = "starting";
|
|
835
|
+
let degraded = false;
|
|
836
|
+
for (const slot of slots) {
|
|
837
|
+
const slotDegraded = await startSlot(slot, slot.agentEnabled, () => options.createEndpoint({
|
|
838
|
+
agentId: slot.definition.agentId,
|
|
839
|
+
cancel: (input) => host.cancelEndpoint(slot.definition.id, input),
|
|
840
|
+
definition: slot.definition,
|
|
841
|
+
endpointId: slot.definition.id,
|
|
842
|
+
handle: (input) => handleEndpoint(slot.definition.id, input),
|
|
843
|
+
instanceId: host.resolveEndpoint(slot.definition.id).instanceId
|
|
844
|
+
}));
|
|
845
|
+
degraded ||= slotDegraded;
|
|
846
|
+
}
|
|
847
|
+
for (const slot of automationSlots) {
|
|
848
|
+
const resolvedDefinition = slot.resolvedDefinition;
|
|
849
|
+
const slotDegraded = await startSlot(slot, slot.agentEnabled && resolvedDefinition !== void 0, async () => {
|
|
850
|
+
if (!options.createAutomation) throw new RivusDeploymentDaemonLifecycleError("deployment bootstrap does not provide Automation adapters");
|
|
851
|
+
if (!resolvedDefinition) throw new RivusDeploymentDaemonLifecycleError(`automation definition not resolved: ${slot.definition.id}`);
|
|
852
|
+
return options.createAutomation({
|
|
853
|
+
automationId: slot.definition.id,
|
|
854
|
+
definition: resolvedDefinition,
|
|
855
|
+
deliveryEndpoint: slotById.get(slot.definition.delivery.endpointId).definition,
|
|
856
|
+
instanceId: host.resolveAutomation(slot.definition.id).instanceId,
|
|
857
|
+
run: (input) => host.handleAutomation(slot.definition.id, {
|
|
858
|
+
invocation: {
|
|
859
|
+
allowedActorOpenIds: [],
|
|
860
|
+
automationId: slot.definition.id,
|
|
861
|
+
endpointId: slot.definition.delivery.endpointId,
|
|
862
|
+
kind: "automation",
|
|
863
|
+
sourceMessageId: input.tickId,
|
|
864
|
+
tenantKey: "automation",
|
|
865
|
+
tickId: input.tickId
|
|
866
|
+
},
|
|
867
|
+
sessionKey: input.sessionKey,
|
|
868
|
+
text: input.text
|
|
869
|
+
})
|
|
870
|
+
});
|
|
871
|
+
});
|
|
872
|
+
degraded ||= slotDegraded;
|
|
873
|
+
}
|
|
874
|
+
lifecycle = degraded ? "degraded" : "running";
|
|
875
|
+
assertRequiredReadiness();
|
|
876
|
+
},
|
|
877
|
+
status,
|
|
878
|
+
stop: async () => {
|
|
879
|
+
if (lifecycle === "stopped") {
|
|
880
|
+
await runtimePool.disposeAll();
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
if (lifecycle !== "running" && lifecycle !== "degraded" && lifecycle !== "cleanup-required") throw new RivusDeploymentDaemonLifecycleError(`cannot stop deployment daemon while ${lifecycle}`);
|
|
884
|
+
lifecycle = "stopping";
|
|
885
|
+
const errors = [];
|
|
886
|
+
await stopSlots(automationSlots, errors);
|
|
887
|
+
await stopSlots(slots, errors);
|
|
888
|
+
try {
|
|
889
|
+
await runtimePool.disposeAll();
|
|
890
|
+
} catch (error) {
|
|
891
|
+
errors.push(error);
|
|
892
|
+
}
|
|
893
|
+
lifecycle = errors.length === 0 ? "stopped" : "cleanup-required";
|
|
894
|
+
if (errors.length === 1) throw errors[0];
|
|
895
|
+
if (errors.length > 1) throw new AggregateError(errors, "deployment daemon cleanup failed");
|
|
896
|
+
}
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
function componentStatus(slot) {
|
|
900
|
+
return {
|
|
901
|
+
enabled: slot.definition.enabled,
|
|
902
|
+
...slot.error ? { error: slot.error } : {},
|
|
903
|
+
lifecycle: slot.lifecycle,
|
|
904
|
+
required: slot.definition.required,
|
|
905
|
+
running: slot.lifecycle === "running" && (slot.adapter?.running() ?? false)
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
function isSlotReady(slot) {
|
|
909
|
+
return !slot.definition.enabled || !slot.definition.required || slot.lifecycle === "running" && (slot.adapter?.running() ?? false);
|
|
910
|
+
}
|
|
911
|
+
async function startSlot(slot, available, create) {
|
|
912
|
+
if (!slot.definition.enabled) {
|
|
913
|
+
slot.lifecycle = "disabled";
|
|
914
|
+
return false;
|
|
915
|
+
}
|
|
916
|
+
if (!available) {
|
|
917
|
+
slot.lifecycle = "disabled";
|
|
918
|
+
return slot.definition.required;
|
|
919
|
+
}
|
|
920
|
+
slot.lifecycle = "starting";
|
|
921
|
+
delete slot.error;
|
|
922
|
+
try {
|
|
923
|
+
slot.adapter ??= await create();
|
|
924
|
+
await slot.adapter.start();
|
|
925
|
+
slot.lifecycle = "running";
|
|
926
|
+
return false;
|
|
927
|
+
} catch (error) {
|
|
928
|
+
slot.error = error instanceof Error ? error.message : String(error);
|
|
929
|
+
slot.lifecycle = "degraded";
|
|
930
|
+
return true;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
async function stopSlots(slots, errors) {
|
|
934
|
+
for (const slot of [...slots].reverse()) {
|
|
935
|
+
if (!slot.adapter || slot.lifecycle === "stopped" || slot.lifecycle === "disabled") continue;
|
|
936
|
+
slot.lifecycle = "stopping";
|
|
937
|
+
try {
|
|
938
|
+
await slot.adapter.stop();
|
|
939
|
+
slot.lifecycle = slot.definition.enabled && slot.agentEnabled ? "stopped" : "disabled";
|
|
940
|
+
delete slot.error;
|
|
941
|
+
} catch (error) {
|
|
942
|
+
errors.push(error);
|
|
943
|
+
slot.error = error instanceof Error ? error.message : String(error);
|
|
944
|
+
slot.lifecycle = "cleanup-required";
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
//#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
|
+
//#region src/infrastructure/runtime/configured-rivus-deployment-daemon.ts
|
|
1148
|
+
async function createConfiguredRivusDeploymentDaemon(options) {
|
|
1149
|
+
const manifest = await loadRivusDeploymentManifest(options.manifestPath, options.manifestOptions);
|
|
1150
|
+
return createRivusDeploymentDaemon({
|
|
1151
|
+
...options.createAutomation ? { createAutomation: options.createAutomation } : {},
|
|
1152
|
+
createEndpoint: options.createEndpoint,
|
|
1153
|
+
createRuntime: options.createRuntime,
|
|
1154
|
+
deploymentRoot: dirname(options.manifestPath),
|
|
1155
|
+
...options.initialInstanceRecords ? { initialInstanceRecords: options.initialInstanceRecords } : {},
|
|
1156
|
+
loadModule: loadNodeRivusPluginModule,
|
|
1157
|
+
manifest
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
//#endregion
|
|
1161
|
+
//#region src/composition/rivus-deployment-cli-process.ts
|
|
1162
|
+
async function createRivusDeploymentCliProcess(factory, context) {
|
|
1163
|
+
const adapters = await factory(context);
|
|
1164
|
+
let adaptersDisposed = false;
|
|
1165
|
+
let recoveryControlPromise;
|
|
1166
|
+
const disposeAdapters = async () => {
|
|
1167
|
+
if (adaptersDisposed) return;
|
|
1168
|
+
adaptersDisposed = true;
|
|
1169
|
+
await adapters.dispose?.();
|
|
1170
|
+
};
|
|
1171
|
+
let daemon;
|
|
1172
|
+
try {
|
|
1173
|
+
daemon = await createConfiguredRivusDeploymentDaemon({
|
|
1174
|
+
...adapters.createAutomation ? { createAutomation: adapters.createAutomation } : {},
|
|
1175
|
+
createEndpoint: adapters.createEndpoint,
|
|
1176
|
+
createRuntime: adapters.createRuntime,
|
|
1177
|
+
...adapters.initialInstanceRecords ? { initialInstanceRecords: adapters.initialInstanceRecords } : {},
|
|
1178
|
+
manifestPath: context.manifestPath
|
|
1179
|
+
});
|
|
1180
|
+
} catch (constructionError) {
|
|
1181
|
+
try {
|
|
1182
|
+
await disposeAdapters();
|
|
1183
|
+
} catch (disposeError) {
|
|
1184
|
+
throw new AggregateError([constructionError, disposeError], "deployment construction and cleanup failed");
|
|
1185
|
+
}
|
|
1186
|
+
throw constructionError;
|
|
1187
|
+
}
|
|
1188
|
+
const start = async () => {
|
|
1189
|
+
if (adaptersDisposed) throw new Error("Rivus deployment process cannot restart after its adapters have been disposed");
|
|
1190
|
+
try {
|
|
1191
|
+
await daemon.start();
|
|
1192
|
+
} catch (startError) {
|
|
1193
|
+
const cleanupErrors = [];
|
|
1194
|
+
try {
|
|
1195
|
+
await daemon.stop();
|
|
1196
|
+
} catch (error) {
|
|
1197
|
+
cleanupErrors.push(error);
|
|
1198
|
+
}
|
|
1199
|
+
try {
|
|
1200
|
+
await disposeAdapters();
|
|
1201
|
+
} catch (error) {
|
|
1202
|
+
cleanupErrors.push(error);
|
|
1203
|
+
}
|
|
1204
|
+
if (cleanupErrors.length > 0) throw new AggregateError([startError, ...cleanupErrors], "deployment startup and cleanup failed");
|
|
1205
|
+
throw startError;
|
|
1206
|
+
}
|
|
1207
|
+
};
|
|
1208
|
+
const stop = async () => {
|
|
1209
|
+
const errors = [];
|
|
1210
|
+
try {
|
|
1211
|
+
await daemon.stop();
|
|
1212
|
+
} catch (error) {
|
|
1213
|
+
errors.push(error);
|
|
1214
|
+
}
|
|
1215
|
+
try {
|
|
1216
|
+
await disposeAdapters();
|
|
1217
|
+
} catch (error) {
|
|
1218
|
+
errors.push(error);
|
|
1219
|
+
}
|
|
1220
|
+
if (errors.length === 1) throw errors[0];
|
|
1221
|
+
if (errors.length > 1) throw new AggregateError(errors, "deployment and adapter cleanup failed");
|
|
1222
|
+
};
|
|
1223
|
+
const process = {
|
|
1224
|
+
defaultSessionKey: `local:${daemon.deployment.manifest.defaultAgentId}:cli`,
|
|
1225
|
+
openRecoveryControl: () => Effect.tryPromise({
|
|
1226
|
+
try: async () => {
|
|
1227
|
+
if (adaptersDisposed) throw new Error("Rivus deployment process cannot open Recovery Control after disposal");
|
|
1228
|
+
if (!adapters.createRecoveryControl) throw new Error("Deployment bootstrap does not expose Recovery Control");
|
|
1229
|
+
recoveryControlPromise ??= Promise.resolve().then(() => adapters.createRecoveryControl());
|
|
1230
|
+
try {
|
|
1231
|
+
return await recoveryControlPromise;
|
|
1232
|
+
} catch (error) {
|
|
1233
|
+
recoveryControlPromise = void 0;
|
|
1234
|
+
throw error;
|
|
1235
|
+
}
|
|
1236
|
+
},
|
|
1237
|
+
catch: (error) => error
|
|
1238
|
+
}),
|
|
1239
|
+
running: () => daemon.running(),
|
|
1240
|
+
start: () => Effect.tryPromise({
|
|
1241
|
+
try: start,
|
|
1242
|
+
catch: (error) => error
|
|
1243
|
+
}),
|
|
1244
|
+
status: () => Effect.sync(() => daemon.status()),
|
|
1245
|
+
stop: () => Effect.tryPromise({
|
|
1246
|
+
try: stop,
|
|
1247
|
+
catch: (error) => error
|
|
1248
|
+
}),
|
|
1249
|
+
promptText: (command) => Effect.tryPromise({
|
|
1250
|
+
try: async () => {
|
|
1251
|
+
if (adaptersDisposed) throw new Error("Rivus deployment process cannot restart after its adapters have been disposed");
|
|
1252
|
+
return readPromptFinalText(await daemon.runDefaultAgent({
|
|
1253
|
+
invocation: {
|
|
1254
|
+
allowedActorOpenIds: [],
|
|
1255
|
+
endpointId: daemon.deployment.manifest.defaultEndpointId,
|
|
1256
|
+
kind: "local-cli",
|
|
1257
|
+
memory: {
|
|
1258
|
+
audience: "private",
|
|
1259
|
+
...context.env.RIVUS_LOCAL_CONVERSATION_ID?.trim() ? { conversationId: context.env.RIVUS_LOCAL_CONVERSATION_ID.trim() } : {},
|
|
1260
|
+
subjectId: context.env.RIVUS_LOCAL_SUBJECT_ID?.trim() || "local-operator",
|
|
1261
|
+
tenantId: context.env.RIVUS_MEMORY_TENANT_ID?.trim() || "local"
|
|
1262
|
+
},
|
|
1263
|
+
sourceMessageId: `cli:${randomUUID()}`,
|
|
1264
|
+
tenantKey: "local"
|
|
1265
|
+
},
|
|
1266
|
+
sessionKey: command.sessionKey,
|
|
1267
|
+
text: command.text
|
|
1268
|
+
}));
|
|
1269
|
+
},
|
|
1270
|
+
catch: (error) => error
|
|
1271
|
+
})
|
|
1272
|
+
};
|
|
1273
|
+
const replayReceiveMessage = adapters.replayReceiveMessage;
|
|
1274
|
+
if (replayReceiveMessage) process.replayReceiveMessage = (payload, options) => Effect.tryPromise({
|
|
1275
|
+
try: async () => {
|
|
1276
|
+
if (!daemon.running()) await start();
|
|
1277
|
+
},
|
|
1278
|
+
catch: (error) => error
|
|
1279
|
+
}).pipe(Effect.flatMap(() => replayReceiveMessage((input) => daemon.handleDefault(input), payload, options)));
|
|
1280
|
+
return process;
|
|
1281
|
+
}
|
|
1282
|
+
function readPromptFinalText(result) {
|
|
1283
|
+
if (typeof result === "string") return result;
|
|
1284
|
+
if (typeof result === "object" && result !== null && "finalText" in result && typeof result.finalText === "string") return result.finalText;
|
|
1285
|
+
throw new Error("Default deployment prompt result must be a string or contain finalText");
|
|
1286
|
+
}
|
|
1287
|
+
//#endregion
|
|
1288
|
+
//#region src/composition/rivus-recovery-cli.ts
|
|
1289
|
+
function createRivusRecoveryCliParser() {
|
|
1290
|
+
const input = { recoveryList: false };
|
|
1291
|
+
return {
|
|
1292
|
+
build: () => buildCommand(input),
|
|
1293
|
+
consume: (argument, next) => consumeArgument(input, argument, next)
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1296
|
+
async function runRivusRecoveryCliCommand(control, command) {
|
|
1297
|
+
switch (command.type) {
|
|
1298
|
+
case "inspect": return Effect.runPromise(control.inspect());
|
|
1299
|
+
case "requeue-dead-letter": return Effect.runPromise(control.requeueDeadLetter({
|
|
1300
|
+
actorId: "local-cli",
|
|
1301
|
+
deliveryId: command.deliveryId,
|
|
1302
|
+
endpointId: command.endpointId,
|
|
1303
|
+
expectedRevision: command.expectedRevision,
|
|
1304
|
+
note: await readPrivateTextFile(command.noteFilePath, "Recovery note", 16 * 1024)
|
|
1305
|
+
}));
|
|
1306
|
+
case "resolve-tool-operation": {
|
|
1307
|
+
const note = await readPrivateTextFile(command.noteFilePath, "Recovery note", 16 * 1024);
|
|
1308
|
+
const outcome = command.outcome.status === "applied" ? {
|
|
1309
|
+
result: parseToolResult(await readPrivateTextFile(command.outcome.resultFilePath, "Tool result", 1024 * 1024)),
|
|
1310
|
+
status: "applied"
|
|
1311
|
+
} : command.outcome;
|
|
1312
|
+
return Effect.runPromise(control.resolveToolOperation({
|
|
1313
|
+
actorId: "local-cli",
|
|
1314
|
+
expectedRevision: command.expectedRevision,
|
|
1315
|
+
instanceId: command.instanceId,
|
|
1316
|
+
note,
|
|
1317
|
+
operationId: command.operationId,
|
|
1318
|
+
outcome
|
|
1319
|
+
}));
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
function consumeArgument(input, argument, next) {
|
|
1324
|
+
if (argument === "--recovery-list") {
|
|
1325
|
+
input.recoveryList = true;
|
|
1326
|
+
return {
|
|
1327
|
+
consumed: 0,
|
|
1328
|
+
handled: true
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
for (const [flag, key] of [
|
|
1332
|
+
["--requeue-dead-letter", "requeueDeadLetterId"],
|
|
1333
|
+
["--resolve-tool-operation", "resolveToolOperationId"],
|
|
1334
|
+
["--endpoint-id", "endpointId"],
|
|
1335
|
+
["--instance-id", "instanceId"],
|
|
1336
|
+
["--expected-revision", "expectedRevision"],
|
|
1337
|
+
["--recovery-note-file", "recoveryNoteFile"],
|
|
1338
|
+
["--tool-outcome", "toolOutcome"],
|
|
1339
|
+
["--tool-result-file", "toolResultFile"]
|
|
1340
|
+
]) {
|
|
1341
|
+
if (argument === flag) {
|
|
1342
|
+
if (!next) return {
|
|
1343
|
+
consumed: 0,
|
|
1344
|
+
error: `${flag} requires a value`,
|
|
1345
|
+
handled: true
|
|
1346
|
+
};
|
|
1347
|
+
input[key] = next;
|
|
1348
|
+
return {
|
|
1349
|
+
consumed: 1,
|
|
1350
|
+
handled: true
|
|
1351
|
+
};
|
|
1352
|
+
}
|
|
1353
|
+
if (argument?.startsWith(`${flag}=`)) {
|
|
1354
|
+
const value = argument.slice(flag.length + 1);
|
|
1355
|
+
if (!value) return {
|
|
1356
|
+
consumed: 0,
|
|
1357
|
+
error: `${flag} requires a value`,
|
|
1358
|
+
handled: true
|
|
1359
|
+
};
|
|
1360
|
+
input[key] = value;
|
|
1361
|
+
return {
|
|
1362
|
+
consumed: 0,
|
|
1363
|
+
handled: true
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
return {
|
|
1368
|
+
consumed: 0,
|
|
1369
|
+
handled: false
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
function buildCommand(input) {
|
|
1373
|
+
const actionCount = Number(input.recoveryList) + Number(input.requeueDeadLetterId !== void 0) + Number(input.resolveToolOperationId !== void 0);
|
|
1374
|
+
const hasOptions = input.endpointId !== void 0 || input.expectedRevision !== void 0 || input.instanceId !== void 0 || input.recoveryNoteFile !== void 0 || input.toolOutcome !== void 0 || input.toolResultFile !== void 0;
|
|
1375
|
+
if (actionCount === 0) return hasOptions ? { error: "Recovery options require --recovery-list, --requeue-dead-letter, or --resolve-tool-operation" } : {};
|
|
1376
|
+
if (actionCount > 1) return { error: "Choose only one recovery command" };
|
|
1377
|
+
if (input.recoveryList) return hasOptions ? { error: "--recovery-list does not accept mutation options" } : { command: { type: "inspect" } };
|
|
1378
|
+
const expectedRevision = parsePositiveInteger(input.expectedRevision);
|
|
1379
|
+
if (expectedRevision === void 0) return { error: "Recovery mutations require --expected-revision with a positive integer" };
|
|
1380
|
+
const noteFilePath = input.recoveryNoteFile?.trim();
|
|
1381
|
+
if (!noteFilePath) return { error: "Recovery mutations require --recovery-note-file" };
|
|
1382
|
+
if (input.requeueDeadLetterId !== void 0) return buildDeadLetterCommand(input, expectedRevision, noteFilePath);
|
|
1383
|
+
return buildToolOperationCommand(input, expectedRevision, noteFilePath);
|
|
1384
|
+
}
|
|
1385
|
+
function buildDeadLetterCommand(input, expectedRevision, noteFilePath) {
|
|
1386
|
+
const endpointId = input.endpointId?.trim();
|
|
1387
|
+
if (!endpointId) return { error: "--requeue-dead-letter requires --endpoint-id" };
|
|
1388
|
+
if (input.instanceId !== void 0 || input.toolOutcome !== void 0 || input.toolResultFile !== void 0) return { error: "--requeue-dead-letter cannot use Tool reconciliation options" };
|
|
1389
|
+
return { command: {
|
|
1390
|
+
deliveryId: input.requeueDeadLetterId,
|
|
1391
|
+
endpointId,
|
|
1392
|
+
expectedRevision,
|
|
1393
|
+
noteFilePath,
|
|
1394
|
+
type: "requeue-dead-letter"
|
|
1395
|
+
} };
|
|
1396
|
+
}
|
|
1397
|
+
function buildToolOperationCommand(input, expectedRevision, noteFilePath) {
|
|
1398
|
+
const instanceId = input.instanceId?.trim();
|
|
1399
|
+
if (!instanceId) return { error: "--resolve-tool-operation requires --instance-id" };
|
|
1400
|
+
if (input.endpointId !== void 0) return { error: "--resolve-tool-operation cannot use --endpoint-id" };
|
|
1401
|
+
if (input.toolOutcome !== "applied" && input.toolOutcome !== "not-applied") return { error: "--resolve-tool-operation requires --tool-outcome applied or not-applied" };
|
|
1402
|
+
if (input.toolOutcome === "not-applied") {
|
|
1403
|
+
if (input.toolResultFile !== void 0) return { error: "--tool-result-file is only valid when --tool-outcome is applied" };
|
|
1404
|
+
return { command: {
|
|
1405
|
+
expectedRevision,
|
|
1406
|
+
instanceId,
|
|
1407
|
+
noteFilePath,
|
|
1408
|
+
operationId: input.resolveToolOperationId,
|
|
1409
|
+
outcome: { status: "not-applied" },
|
|
1410
|
+
type: "resolve-tool-operation"
|
|
1411
|
+
} };
|
|
1412
|
+
}
|
|
1413
|
+
const resultFilePath = input.toolResultFile?.trim();
|
|
1414
|
+
if (!resultFilePath) return { error: "--tool-outcome applied requires --tool-result-file" };
|
|
1415
|
+
return { command: {
|
|
1416
|
+
expectedRevision,
|
|
1417
|
+
instanceId,
|
|
1418
|
+
noteFilePath,
|
|
1419
|
+
operationId: input.resolveToolOperationId,
|
|
1420
|
+
outcome: {
|
|
1421
|
+
resultFilePath,
|
|
1422
|
+
status: "applied"
|
|
1423
|
+
},
|
|
1424
|
+
type: "resolve-tool-operation"
|
|
1425
|
+
} };
|
|
1426
|
+
}
|
|
1427
|
+
function parsePositiveInteger(value) {
|
|
1428
|
+
return value && /^[1-9]\d*$/.test(value) ? Number(value) : void 0;
|
|
1429
|
+
}
|
|
1430
|
+
async function readPrivateTextFile(filePath, label, maxBytes) {
|
|
1431
|
+
const handle = await open(filePath, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
1432
|
+
try {
|
|
1433
|
+
const metadata = await handle.stat();
|
|
1434
|
+
if (!metadata.isFile()) throw new Error(`${label} must be a regular file`);
|
|
1435
|
+
if ((metadata.mode & 63) !== 0) throw new Error(`${label} file permissions must be 0600 or stricter`);
|
|
1436
|
+
if (metadata.size > maxBytes) throw new Error(`${label} file exceeds ${maxBytes} bytes`);
|
|
1437
|
+
return (await handle.readFile("utf8")).trim();
|
|
1438
|
+
} finally {
|
|
1439
|
+
await handle.close();
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
function parseToolResult(value) {
|
|
1443
|
+
try {
|
|
1444
|
+
return JSON.parse(value);
|
|
1445
|
+
} catch {
|
|
1446
|
+
throw new Error("Tool result file must contain valid JSON");
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
//#endregion
|
|
1450
|
+
//#region src/composition/rivus-daemon-cli.ts
|
|
1451
|
+
const USAGE = `Usage: rivus --bootstrap <module> [--manifest <rivus.config.json>]
|
|
1452
|
+
|
|
1453
|
+
Starts a local Rivus Agent daemon from an injected bootstrap module.
|
|
1454
|
+
|
|
1455
|
+
Options:
|
|
1456
|
+
--bootstrap <module> Module exporting createRivusDaemonProcess(context)
|
|
1457
|
+
--manifest <path> Start the manifest-driven multi-agent deployment; bootstrap exports createRivusDeploymentAdapters(context)
|
|
1458
|
+
--env-file <path> Load local KEY=value config before reading environment
|
|
1459
|
+
--check-config Print redacted local config JSON and exit
|
|
1460
|
+
--prompt <text> Run one local prompt through bootstrap promptText() and exit
|
|
1461
|
+
--replay-feishu-event <json>
|
|
1462
|
+
Replay one Feishu receive-message payload through bootstrap replayReceiveMessage() and exit
|
|
1463
|
+
--replay-feishu-text <text>
|
|
1464
|
+
Replay one synthetic Feishu receive-message text payload without Feishu side effects and exit
|
|
1465
|
+
--feishu-message-id <id>
|
|
1466
|
+
Message id for --replay-feishu-text (default om_cli_<timestamp>)
|
|
1467
|
+
--feishu-chat-id <id> Chat id for --replay-feishu-text (default oc_cli)
|
|
1468
|
+
--feishu-thread-id <id>
|
|
1469
|
+
Thread id for --replay-feishu-text (default omt_cli)
|
|
1470
|
+
--feishu-tenant-key <key>
|
|
1471
|
+
Tenant key for --replay-feishu-text (default tenant_cli)
|
|
1472
|
+
--print-openclaw-env <json>
|
|
1473
|
+
Print a Rivus env file from an OpenClaw config JSON and exit
|
|
1474
|
+
--pi-api-key-file <path>
|
|
1475
|
+
Add PI_API_KEY_FILE when using --print-openclaw-env
|
|
1476
|
+
--session-key <key> Session key for --prompt (default local:<agent-id>:cli)
|
|
1477
|
+
--status Print bootstrap status JSON without starting the daemon
|
|
1478
|
+
--recovery-list List Dead Letters and Tool operations requiring reconciliation
|
|
1479
|
+
--requeue-dead-letter <message-id>
|
|
1480
|
+
Requeue one Dead Letter; requires --endpoint-id, --expected-revision, and --recovery-note-file
|
|
1481
|
+
--resolve-tool-operation <operation-id>
|
|
1482
|
+
Resolve one uncertain Tool operation; requires --instance-id, --expected-revision,
|
|
1483
|
+
--tool-outcome, and --recovery-note-file
|
|
1484
|
+
--endpoint-id <id> Endpoint owning the Dead Letter
|
|
1485
|
+
--instance-id <id> Agent Instance owning the Tool operation
|
|
1486
|
+
--expected-revision <n>
|
|
1487
|
+
Exact record revision required by a recovery mutation
|
|
1488
|
+
--recovery-note-file <path>
|
|
1489
|
+
Private (0600) file containing the required audit note
|
|
1490
|
+
--tool-outcome <applied|not-applied>
|
|
1491
|
+
Confirm whether the external Tool effect occurred
|
|
1492
|
+
--tool-result-file <path>
|
|
1493
|
+
Private (0600) JSON file required when --tool-outcome is applied
|
|
1494
|
+
--status-url <url> Print live daemon status JSON from a running daemon
|
|
1495
|
+
--wait-receive <kind> Wait with --status-url or a started bootstrap daemon until receive.lastAccepted or receive.lastHandled exists
|
|
1496
|
+
--wait-receive-text <text>
|
|
1497
|
+
With --wait-receive handled, wait until receive.lastHandled.intake.text contains text
|
|
1498
|
+
--wait-receive-message-id <id>
|
|
1499
|
+
With --wait-receive, wait until the accepted or handled receive observation has this Feishu message id
|
|
1500
|
+
--wait-receive-observed-after <iso>
|
|
1501
|
+
With --wait-receive, ignore receive observations at or before this ISO timestamp
|
|
1502
|
+
--wait-timeout-ms <n> Timeout for --wait-receive (default 30000)
|
|
1503
|
+
--wait-poll-ms <n> Poll interval for --wait-receive (default 500)
|
|
1504
|
+
--help Show this help
|
|
1505
|
+
|
|
1506
|
+
Environment:
|
|
1507
|
+
RIVUS_BOOTSTRAP_MODULE may be used instead of --bootstrap.
|
|
1508
|
+
FEISHU_APP_ID and FEISHU_APP_SECRET are required by the default config loader.
|
|
1509
|
+
PI_API_KEY_FILE may point to a local BYOK key file instead of PI_API_KEY.
|
|
1510
|
+
`;
|
|
1511
|
+
const DEFAULT_WAIT_RECEIVE_TIMEOUT_MS = 3e4;
|
|
1512
|
+
const DEFAULT_WAIT_RECEIVE_POLL_MS = 500;
|
|
1513
|
+
function runRivusDaemonCli(options) {
|
|
1514
|
+
return Effect.promise(async () => {
|
|
1515
|
+
const parsed = parseArgs(options.argv);
|
|
1516
|
+
if (parsed.help) {
|
|
1517
|
+
options.stdout.write(USAGE);
|
|
1518
|
+
return 0;
|
|
1519
|
+
}
|
|
1520
|
+
if (parsed.error) {
|
|
1521
|
+
options.stderr.write(`${parsed.error}\n\n${USAGE}`);
|
|
1522
|
+
return 1;
|
|
1523
|
+
}
|
|
1524
|
+
if (parsed.statusUrl) try {
|
|
1525
|
+
const status = parsed.waitReceive ? await waitForLiveReceiveStatus(parsed.statusUrl, parsed.waitReceive, {
|
|
1526
|
+
...parsed.waitReceiveMessageId ? { messageId: parsed.waitReceiveMessageId } : {},
|
|
1527
|
+
...parsed.waitReceiveObservedAfter ? { observedAfter: parsed.waitReceiveObservedAfter } : {},
|
|
1528
|
+
pollMs: parsed.waitPollMs ?? DEFAULT_WAIT_RECEIVE_POLL_MS,
|
|
1529
|
+
...parsed.waitReceiveText ? { text: parsed.waitReceiveText } : {},
|
|
1530
|
+
timeoutMs: parsed.waitTimeoutMs ?? DEFAULT_WAIT_RECEIVE_TIMEOUT_MS
|
|
1531
|
+
}) : await fetchLiveStatus(parsed.statusUrl);
|
|
1532
|
+
options.stdout.write(`${JSON.stringify(status, null, 2)}\n`);
|
|
1533
|
+
return 0;
|
|
1534
|
+
} catch (error) {
|
|
1535
|
+
options.stderr.write(`${formatCliError(error)}\n`);
|
|
1536
|
+
return 1;
|
|
1537
|
+
}
|
|
1538
|
+
if (parsed.printOpenClawEnvPath) try {
|
|
1539
|
+
const result = createRivusEnvFromOpenClawConfig(JSON.parse(await readFile(parsed.printOpenClawEnvPath, "utf8")), parsed.piApiKeyFile ? { piApiKeyFile: parsed.piApiKeyFile } : {});
|
|
1540
|
+
for (const warning of result.warnings) options.stderr.write(`Warning: ${warning}\n`);
|
|
1541
|
+
options.stdout.write(formatRivusEnvFile(result.env));
|
|
1542
|
+
return 0;
|
|
1543
|
+
} catch (error) {
|
|
1544
|
+
options.stderr.write(`${formatCliError(error)}\n`);
|
|
1545
|
+
return 1;
|
|
1546
|
+
}
|
|
1547
|
+
let env;
|
|
1548
|
+
try {
|
|
1549
|
+
env = await loadCliEnv(parsed.envFilePath, options.env);
|
|
1550
|
+
} catch (error) {
|
|
1551
|
+
options.stderr.write(`${formatCliError(error)}\n`);
|
|
1552
|
+
return 1;
|
|
1553
|
+
}
|
|
1554
|
+
const bootstrapSpecifier = parsed.bootstrap ?? env.RIVUS_BOOTSTRAP_MODULE?.trim();
|
|
1555
|
+
if (!parsed.checkConfig && !bootstrapSpecifier) {
|
|
1556
|
+
options.stderr.write(`Missing --bootstrap <module> or RIVUS_BOOTSTRAP_MODULE\n\n${USAGE}`);
|
|
1557
|
+
return 1;
|
|
1558
|
+
}
|
|
1559
|
+
let legacyConfig;
|
|
1560
|
+
if (parsed.manifestPath) {
|
|
1561
|
+
if (parsed.checkConfig) try {
|
|
1562
|
+
const manifest = await loadRivusDeploymentManifest(parsed.manifestPath);
|
|
1563
|
+
validateRivusDeploymentManifest(manifest);
|
|
1564
|
+
options.stdout.write(`${JSON.stringify(toRedactedDeploymentManifest(manifest), null, 2)}\n`);
|
|
1565
|
+
return 0;
|
|
1566
|
+
} catch (error) {
|
|
1567
|
+
options.stderr.write(`${formatCliError(error)}\n`);
|
|
1568
|
+
return 1;
|
|
1569
|
+
}
|
|
1570
|
+
} else {
|
|
1571
|
+
const configExit = await Effect.runPromiseExit(loadRivusDaemonConfig(env));
|
|
1572
|
+
if (configExit._tag === "Failure") {
|
|
1573
|
+
options.stderr.write(`${configExit.cause.toString()}\n`);
|
|
1574
|
+
return 1;
|
|
1575
|
+
}
|
|
1576
|
+
legacyConfig = configExit.value;
|
|
1577
|
+
if (parsed.checkConfig) {
|
|
1578
|
+
options.stdout.write(`${JSON.stringify(toRedactedConfig(legacyConfig), null, 2)}\n`);
|
|
1579
|
+
return 0;
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
if (!bootstrapSpecifier) {
|
|
1583
|
+
options.stderr.write(`Missing --bootstrap <module> or RIVUS_BOOTSTRAP_MODULE\n\n${USAGE}`);
|
|
1584
|
+
return 1;
|
|
1585
|
+
}
|
|
1586
|
+
try {
|
|
1587
|
+
const module = await (options.loadBootstrap ?? ((specifier) => import(specifier)))(bootstrapSpecifier);
|
|
1588
|
+
const daemon = parsed.manifestPath ? await createCliDeploymentDaemon(module.createRivusDeploymentAdapters, {
|
|
1589
|
+
argv: options.argv,
|
|
1590
|
+
env,
|
|
1591
|
+
manifestPath: parsed.manifestPath
|
|
1592
|
+
}) : await createCliLegacyDaemon(module, {
|
|
1593
|
+
argv: options.argv,
|
|
1594
|
+
config: legacyConfig,
|
|
1595
|
+
env
|
|
1596
|
+
});
|
|
1597
|
+
if (parsed.recoveryCommand) {
|
|
1598
|
+
const recoveryCommand = parsed.recoveryCommand;
|
|
1599
|
+
return await runOneShotDaemon(daemon, async () => {
|
|
1600
|
+
if (!hasRecoveryRunner(daemon)) {
|
|
1601
|
+
options.stderr.write(`Bootstrap daemon does not expose openRecoveryControl()\n`);
|
|
1602
|
+
return 1;
|
|
1603
|
+
}
|
|
1604
|
+
const result = await runRivusRecoveryCliCommand(await Effect.runPromise(daemon.openRecoveryControl()), recoveryCommand);
|
|
1605
|
+
options.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
1606
|
+
return 0;
|
|
1607
|
+
});
|
|
1608
|
+
}
|
|
1609
|
+
if (parsed.status) return await runOneShotDaemon(daemon, async () => {
|
|
1610
|
+
if (!hasStatusReporter(daemon)) {
|
|
1611
|
+
options.stderr.write(`Bootstrap daemon does not expose status()\n`);
|
|
1612
|
+
return 1;
|
|
1613
|
+
}
|
|
1614
|
+
const status = await Effect.runPromise(daemon.status());
|
|
1615
|
+
options.stdout.write(`${JSON.stringify(status, null, 2)}\n`);
|
|
1616
|
+
return 0;
|
|
1617
|
+
});
|
|
1618
|
+
if (parsed.prompt !== void 0) {
|
|
1619
|
+
const prompt = parsed.prompt;
|
|
1620
|
+
return await runOneShotDaemon(daemon, async () => {
|
|
1621
|
+
if (!hasPromptRunner(daemon)) {
|
|
1622
|
+
options.stderr.write(`Bootstrap daemon does not expose promptText(command)\n`);
|
|
1623
|
+
return 1;
|
|
1624
|
+
}
|
|
1625
|
+
const text = await Effect.runPromise(daemon.promptText({
|
|
1626
|
+
sessionKey: parsed.sessionKey ?? daemon.defaultSessionKey ?? `local:${legacyConfig.agentId}:cli`,
|
|
1627
|
+
text: prompt
|
|
1628
|
+
}));
|
|
1629
|
+
options.stdout.write(`${text}\n`);
|
|
1630
|
+
return 0;
|
|
1631
|
+
});
|
|
1632
|
+
}
|
|
1633
|
+
if (parsed.replayFeishuText !== void 0) {
|
|
1634
|
+
const replayFeishuText = parsed.replayFeishuText;
|
|
1635
|
+
return await runOneShotDaemon(daemon, async () => {
|
|
1636
|
+
if (!hasFeishuReplayRunner(daemon)) {
|
|
1637
|
+
options.stderr.write(`Bootstrap daemon does not expose replayReceiveMessage(payload)\n`);
|
|
1638
|
+
return 1;
|
|
1639
|
+
}
|
|
1640
|
+
const payload = createSyntheticFeishuTextReplayPayload(replayFeishuText, {
|
|
1641
|
+
...parsed.feishuReplayChatId !== void 0 ? { chatId: parsed.feishuReplayChatId } : {},
|
|
1642
|
+
...parsed.feishuReplayMessageId !== void 0 ? { messageId: parsed.feishuReplayMessageId } : {},
|
|
1643
|
+
...parsed.feishuReplayTenantKey !== void 0 ? { tenantKey: parsed.feishuReplayTenantKey } : {},
|
|
1644
|
+
...parsed.feishuReplayThreadId !== void 0 ? { threadId: parsed.feishuReplayThreadId } : {}
|
|
1645
|
+
});
|
|
1646
|
+
const result = await Effect.runPromise(daemon.replayReceiveMessage(payload, { sideEffects: "disabled" }));
|
|
1647
|
+
options.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
1648
|
+
return 0;
|
|
1649
|
+
});
|
|
1650
|
+
}
|
|
1651
|
+
if (parsed.replayFeishuEventPath !== void 0) {
|
|
1652
|
+
const replayFeishuEventPath = parsed.replayFeishuEventPath;
|
|
1653
|
+
return await runOneShotDaemon(daemon, async () => {
|
|
1654
|
+
if (!hasFeishuReplayRunner(daemon)) {
|
|
1655
|
+
options.stderr.write(`Bootstrap daemon does not expose replayReceiveMessage(payload)\n`);
|
|
1656
|
+
return 1;
|
|
1657
|
+
}
|
|
1658
|
+
const payload = await readFeishuReceiveMessagePayload(replayFeishuEventPath);
|
|
1659
|
+
const result = await Effect.runPromise(daemon.replayReceiveMessage(payload));
|
|
1660
|
+
options.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
1661
|
+
return 0;
|
|
1662
|
+
});
|
|
1663
|
+
}
|
|
1664
|
+
if (parsed.waitReceive) {
|
|
1665
|
+
const waitReceive = parsed.waitReceive;
|
|
1666
|
+
return await runOneShotDaemon(daemon, async () => {
|
|
1667
|
+
if (!hasStatusReporter(daemon)) {
|
|
1668
|
+
options.stderr.write(`Bootstrap daemon does not expose status()\n`);
|
|
1669
|
+
return 1;
|
|
1670
|
+
}
|
|
1671
|
+
installCliShutdownController(options, daemon);
|
|
1672
|
+
await Effect.runPromise(daemon.start());
|
|
1673
|
+
const status = await waitForReceiveStatus(() => Effect.runPromise(daemon.status()), waitReceive, {
|
|
1674
|
+
...parsed.waitReceiveMessageId ? { messageId: parsed.waitReceiveMessageId } : {},
|
|
1675
|
+
...parsed.waitReceiveObservedAfter ? { observedAfter: parsed.waitReceiveObservedAfter } : {},
|
|
1676
|
+
pollMs: parsed.waitPollMs ?? DEFAULT_WAIT_RECEIVE_POLL_MS,
|
|
1677
|
+
...parsed.waitReceiveText ? { text: parsed.waitReceiveText } : {},
|
|
1678
|
+
timeoutMs: parsed.waitTimeoutMs ?? DEFAULT_WAIT_RECEIVE_TIMEOUT_MS
|
|
1679
|
+
});
|
|
1680
|
+
options.stdout.write(`${JSON.stringify(status, null, 2)}\n`);
|
|
1681
|
+
return 0;
|
|
1682
|
+
});
|
|
1683
|
+
}
|
|
1684
|
+
installCliShutdownController(options, daemon);
|
|
1685
|
+
await Effect.runPromise(daemon.start());
|
|
1686
|
+
options.stdout.write("Rivus Agent daemon started\n");
|
|
1687
|
+
return 0;
|
|
1688
|
+
} catch (error) {
|
|
1689
|
+
options.stderr.write(`${formatCliError(error)}\n`);
|
|
1690
|
+
return 1;
|
|
1691
|
+
}
|
|
1692
|
+
});
|
|
1693
|
+
}
|
|
1694
|
+
async function createCliLegacyDaemon(module, context) {
|
|
1695
|
+
const factory = module.createRivusDaemonProcess ?? module.default;
|
|
1696
|
+
if (!factory) throw new Error("Bootstrap module must export createRivusDaemonProcess(context) or a default factory");
|
|
1697
|
+
return factory(context);
|
|
1698
|
+
}
|
|
1699
|
+
async function createCliDeploymentDaemon(factory, context) {
|
|
1700
|
+
if (!factory) throw new Error("Manifest bootstrap module must export createRivusDeploymentAdapters(context)");
|
|
1701
|
+
return createRivusDeploymentCliProcess(factory, context);
|
|
1702
|
+
}
|
|
1703
|
+
async function runOneShotDaemon(daemon, action) {
|
|
1704
|
+
try {
|
|
1705
|
+
return await action();
|
|
1706
|
+
} finally {
|
|
1707
|
+
await Effect.runPromise(daemon.stop());
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
function installCliShutdownController(options, daemon) {
|
|
1711
|
+
createRivusDaemonShutdownController({
|
|
1712
|
+
daemon,
|
|
1713
|
+
onError: (error, signal) => {
|
|
1714
|
+
options.stderr.write(`Failed to stop daemon after ${signal}: ${formatCliError(error)}\n`);
|
|
1715
|
+
options.exitAfterSignal?.(1);
|
|
1716
|
+
},
|
|
1717
|
+
onStopped: () => {
|
|
1718
|
+
options.exitAfterSignal?.(0);
|
|
1719
|
+
},
|
|
1720
|
+
signalSource: options.signalSource
|
|
1721
|
+
}).install();
|
|
1722
|
+
}
|
|
1723
|
+
function parseArgs(argv) {
|
|
1724
|
+
let bootstrap;
|
|
1725
|
+
let checkConfig = false;
|
|
1726
|
+
let envFilePath;
|
|
1727
|
+
let feishuReplayChatId;
|
|
1728
|
+
let feishuReplayMessageId;
|
|
1729
|
+
let feishuReplayTenantKey;
|
|
1730
|
+
let feishuReplayThreadId;
|
|
1731
|
+
let manifestPath;
|
|
1732
|
+
let piApiKeyFile;
|
|
1733
|
+
let prompt;
|
|
1734
|
+
let printOpenClawEnvPath;
|
|
1735
|
+
let replayFeishuEventPath;
|
|
1736
|
+
let replayFeishuText;
|
|
1737
|
+
const recoveryCli = createRivusRecoveryCliParser();
|
|
1738
|
+
let sessionKey;
|
|
1739
|
+
let status = false;
|
|
1740
|
+
let statusUrl;
|
|
1741
|
+
let waitPollMs;
|
|
1742
|
+
let waitReceive;
|
|
1743
|
+
let waitReceiveMessageId;
|
|
1744
|
+
let waitReceiveObservedAfter;
|
|
1745
|
+
let waitReceiveText;
|
|
1746
|
+
let waitTimeoutMs;
|
|
1747
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
1748
|
+
const arg = argv[index];
|
|
1749
|
+
if (arg === "--help" || arg === "-h") return {
|
|
1750
|
+
help: true,
|
|
1751
|
+
status,
|
|
1752
|
+
...statusUrl ? { statusUrl } : {}
|
|
1753
|
+
};
|
|
1754
|
+
if (arg === "--status") {
|
|
1755
|
+
status = true;
|
|
1756
|
+
continue;
|
|
1757
|
+
}
|
|
1758
|
+
const recoveryArgument = recoveryCli.consume(arg, argv[index + 1]);
|
|
1759
|
+
if (recoveryArgument.handled) {
|
|
1760
|
+
if (recoveryArgument.error) return {
|
|
1761
|
+
error: recoveryArgument.error,
|
|
1762
|
+
help: false,
|
|
1763
|
+
status
|
|
1764
|
+
};
|
|
1765
|
+
index += recoveryArgument.consumed;
|
|
1766
|
+
continue;
|
|
1767
|
+
}
|
|
1768
|
+
if (arg === "--check-config") {
|
|
1769
|
+
checkConfig = true;
|
|
1770
|
+
continue;
|
|
1771
|
+
}
|
|
1772
|
+
if (arg === "--env-file") {
|
|
1773
|
+
const value = argv[index + 1];
|
|
1774
|
+
if (!value) return {
|
|
1775
|
+
error: "--env-file requires a path",
|
|
1776
|
+
help: false,
|
|
1777
|
+
status
|
|
1778
|
+
};
|
|
1779
|
+
envFilePath = value;
|
|
1780
|
+
index += 1;
|
|
1781
|
+
continue;
|
|
1782
|
+
}
|
|
1783
|
+
if (arg?.startsWith("--env-file=")) {
|
|
1784
|
+
envFilePath = arg.slice(11);
|
|
1785
|
+
if (!envFilePath) return {
|
|
1786
|
+
error: "--env-file requires a path",
|
|
1787
|
+
help: false,
|
|
1788
|
+
status
|
|
1789
|
+
};
|
|
1790
|
+
continue;
|
|
1791
|
+
}
|
|
1792
|
+
if (arg === "--prompt") {
|
|
1793
|
+
const value = argv[index + 1];
|
|
1794
|
+
if (!value) return {
|
|
1795
|
+
error: "--prompt requires text",
|
|
1796
|
+
help: false,
|
|
1797
|
+
status
|
|
1798
|
+
};
|
|
1799
|
+
prompt = value;
|
|
1800
|
+
index += 1;
|
|
1801
|
+
continue;
|
|
1802
|
+
}
|
|
1803
|
+
if (arg?.startsWith("--prompt=")) {
|
|
1804
|
+
prompt = arg.slice(9);
|
|
1805
|
+
if (!prompt) return {
|
|
1806
|
+
error: "--prompt requires text",
|
|
1807
|
+
help: false,
|
|
1808
|
+
status
|
|
1809
|
+
};
|
|
1810
|
+
continue;
|
|
1811
|
+
}
|
|
1812
|
+
if (arg === "--session-key") {
|
|
1813
|
+
const value = argv[index + 1];
|
|
1814
|
+
if (!value) return {
|
|
1815
|
+
error: "--session-key requires a value",
|
|
1816
|
+
help: false,
|
|
1817
|
+
status
|
|
1818
|
+
};
|
|
1819
|
+
sessionKey = value;
|
|
1820
|
+
index += 1;
|
|
1821
|
+
continue;
|
|
1822
|
+
}
|
|
1823
|
+
if (arg === "--print-openclaw-env") {
|
|
1824
|
+
const value = argv[index + 1];
|
|
1825
|
+
if (!value) return {
|
|
1826
|
+
error: "--print-openclaw-env requires a JSON file path",
|
|
1827
|
+
help: false,
|
|
1828
|
+
status
|
|
1829
|
+
};
|
|
1830
|
+
printOpenClawEnvPath = value;
|
|
1831
|
+
index += 1;
|
|
1832
|
+
continue;
|
|
1833
|
+
}
|
|
1834
|
+
if (arg?.startsWith("--print-openclaw-env=")) {
|
|
1835
|
+
printOpenClawEnvPath = arg.slice(21);
|
|
1836
|
+
if (!printOpenClawEnvPath) return {
|
|
1837
|
+
error: "--print-openclaw-env requires a JSON file path",
|
|
1838
|
+
help: false,
|
|
1839
|
+
status
|
|
1840
|
+
};
|
|
1841
|
+
continue;
|
|
1842
|
+
}
|
|
1843
|
+
if (arg === "--pi-api-key-file") {
|
|
1844
|
+
const value = argv[index + 1];
|
|
1845
|
+
if (!value) return {
|
|
1846
|
+
error: "--pi-api-key-file requires a path",
|
|
1847
|
+
help: false,
|
|
1848
|
+
status
|
|
1849
|
+
};
|
|
1850
|
+
piApiKeyFile = value;
|
|
1851
|
+
index += 1;
|
|
1852
|
+
continue;
|
|
1853
|
+
}
|
|
1854
|
+
if (arg?.startsWith("--pi-api-key-file=")) {
|
|
1855
|
+
piApiKeyFile = arg.slice(18);
|
|
1856
|
+
if (!piApiKeyFile) return {
|
|
1857
|
+
error: "--pi-api-key-file requires a path",
|
|
1858
|
+
help: false,
|
|
1859
|
+
status
|
|
1860
|
+
};
|
|
1861
|
+
continue;
|
|
1862
|
+
}
|
|
1863
|
+
if (arg === "--replay-feishu-event") {
|
|
1864
|
+
const value = argv[index + 1];
|
|
1865
|
+
if (!value) return {
|
|
1866
|
+
error: "--replay-feishu-event requires a JSON file path",
|
|
1867
|
+
help: false,
|
|
1868
|
+
status
|
|
1869
|
+
};
|
|
1870
|
+
replayFeishuEventPath = value;
|
|
1871
|
+
index += 1;
|
|
1872
|
+
continue;
|
|
1873
|
+
}
|
|
1874
|
+
if (arg?.startsWith("--replay-feishu-event=")) {
|
|
1875
|
+
replayFeishuEventPath = arg.slice(22);
|
|
1876
|
+
if (!replayFeishuEventPath) return {
|
|
1877
|
+
error: "--replay-feishu-event requires a JSON file path",
|
|
1878
|
+
help: false,
|
|
1879
|
+
status
|
|
1880
|
+
};
|
|
1881
|
+
continue;
|
|
1882
|
+
}
|
|
1883
|
+
if (arg === "--replay-feishu-text") {
|
|
1884
|
+
const value = argv[index + 1];
|
|
1885
|
+
if (!value) return {
|
|
1886
|
+
error: "--replay-feishu-text requires text",
|
|
1887
|
+
help: false,
|
|
1888
|
+
status
|
|
1889
|
+
};
|
|
1890
|
+
replayFeishuText = value;
|
|
1891
|
+
index += 1;
|
|
1892
|
+
continue;
|
|
1893
|
+
}
|
|
1894
|
+
if (arg?.startsWith("--replay-feishu-text=")) {
|
|
1895
|
+
replayFeishuText = arg.slice(21);
|
|
1896
|
+
if (!replayFeishuText) return {
|
|
1897
|
+
error: "--replay-feishu-text requires text",
|
|
1898
|
+
help: false,
|
|
1899
|
+
status
|
|
1900
|
+
};
|
|
1901
|
+
continue;
|
|
1902
|
+
}
|
|
1903
|
+
if (arg === "--feishu-message-id") {
|
|
1904
|
+
const value = argv[index + 1];
|
|
1905
|
+
if (!value) return {
|
|
1906
|
+
error: "--feishu-message-id requires a value",
|
|
1907
|
+
help: false,
|
|
1908
|
+
status
|
|
1909
|
+
};
|
|
1910
|
+
feishuReplayMessageId = value;
|
|
1911
|
+
index += 1;
|
|
1912
|
+
continue;
|
|
1913
|
+
}
|
|
1914
|
+
if (arg?.startsWith("--feishu-message-id=")) {
|
|
1915
|
+
feishuReplayMessageId = arg.slice(20);
|
|
1916
|
+
if (!feishuReplayMessageId) return {
|
|
1917
|
+
error: "--feishu-message-id requires a value",
|
|
1918
|
+
help: false,
|
|
1919
|
+
status
|
|
1920
|
+
};
|
|
1921
|
+
continue;
|
|
1922
|
+
}
|
|
1923
|
+
if (arg === "--feishu-chat-id") {
|
|
1924
|
+
const value = argv[index + 1];
|
|
1925
|
+
if (!value) return {
|
|
1926
|
+
error: "--feishu-chat-id requires a value",
|
|
1927
|
+
help: false,
|
|
1928
|
+
status
|
|
1929
|
+
};
|
|
1930
|
+
feishuReplayChatId = value;
|
|
1931
|
+
index += 1;
|
|
1932
|
+
continue;
|
|
1933
|
+
}
|
|
1934
|
+
if (arg?.startsWith("--feishu-chat-id=")) {
|
|
1935
|
+
feishuReplayChatId = arg.slice(17);
|
|
1936
|
+
if (!feishuReplayChatId) return {
|
|
1937
|
+
error: "--feishu-chat-id requires a value",
|
|
1938
|
+
help: false,
|
|
1939
|
+
status
|
|
1940
|
+
};
|
|
1941
|
+
continue;
|
|
1942
|
+
}
|
|
1943
|
+
if (arg === "--feishu-thread-id") {
|
|
1944
|
+
const value = argv[index + 1];
|
|
1945
|
+
if (!value) return {
|
|
1946
|
+
error: "--feishu-thread-id requires a value",
|
|
1947
|
+
help: false,
|
|
1948
|
+
status
|
|
1949
|
+
};
|
|
1950
|
+
feishuReplayThreadId = value;
|
|
1951
|
+
index += 1;
|
|
1952
|
+
continue;
|
|
1953
|
+
}
|
|
1954
|
+
if (arg?.startsWith("--feishu-thread-id=")) {
|
|
1955
|
+
feishuReplayThreadId = arg.slice(19);
|
|
1956
|
+
if (!feishuReplayThreadId) return {
|
|
1957
|
+
error: "--feishu-thread-id requires a value",
|
|
1958
|
+
help: false,
|
|
1959
|
+
status
|
|
1960
|
+
};
|
|
1961
|
+
continue;
|
|
1962
|
+
}
|
|
1963
|
+
if (arg === "--feishu-tenant-key") {
|
|
1964
|
+
const value = argv[index + 1];
|
|
1965
|
+
if (!value) return {
|
|
1966
|
+
error: "--feishu-tenant-key requires a value",
|
|
1967
|
+
help: false,
|
|
1968
|
+
status
|
|
1969
|
+
};
|
|
1970
|
+
feishuReplayTenantKey = value;
|
|
1971
|
+
index += 1;
|
|
1972
|
+
continue;
|
|
1973
|
+
}
|
|
1974
|
+
if (arg?.startsWith("--feishu-tenant-key=")) {
|
|
1975
|
+
feishuReplayTenantKey = arg.slice(20);
|
|
1976
|
+
if (!feishuReplayTenantKey) return {
|
|
1977
|
+
error: "--feishu-tenant-key requires a value",
|
|
1978
|
+
help: false,
|
|
1979
|
+
status
|
|
1980
|
+
};
|
|
1981
|
+
continue;
|
|
1982
|
+
}
|
|
1983
|
+
if (arg?.startsWith("--session-key=")) {
|
|
1984
|
+
sessionKey = arg.slice(14);
|
|
1985
|
+
if (!sessionKey) return {
|
|
1986
|
+
error: "--session-key requires a value",
|
|
1987
|
+
help: false,
|
|
1988
|
+
status
|
|
1989
|
+
};
|
|
1990
|
+
continue;
|
|
1991
|
+
}
|
|
1992
|
+
if (arg === "--status-url") {
|
|
1993
|
+
const value = argv[index + 1];
|
|
1994
|
+
if (!value) return {
|
|
1995
|
+
error: "--status-url requires a URL",
|
|
1996
|
+
help: false,
|
|
1997
|
+
status
|
|
1998
|
+
};
|
|
1999
|
+
statusUrl = value;
|
|
2000
|
+
index += 1;
|
|
2001
|
+
continue;
|
|
2002
|
+
}
|
|
2003
|
+
if (arg?.startsWith("--status-url=")) {
|
|
2004
|
+
statusUrl = arg.slice(13);
|
|
2005
|
+
if (!statusUrl) return {
|
|
2006
|
+
error: "--status-url requires a URL",
|
|
2007
|
+
help: false,
|
|
2008
|
+
status
|
|
2009
|
+
};
|
|
2010
|
+
continue;
|
|
2011
|
+
}
|
|
2012
|
+
if (arg === "--wait-receive") {
|
|
2013
|
+
const parsedWait = parseWaitReceive(argv[index + 1]);
|
|
2014
|
+
if (!parsedWait) return {
|
|
2015
|
+
error: "--wait-receive requires accepted or handled",
|
|
2016
|
+
help: false,
|
|
2017
|
+
status
|
|
2018
|
+
};
|
|
2019
|
+
waitReceive = parsedWait;
|
|
2020
|
+
index += 1;
|
|
2021
|
+
continue;
|
|
2022
|
+
}
|
|
2023
|
+
if (arg?.startsWith("--wait-receive=")) {
|
|
2024
|
+
const parsedWait = parseWaitReceive(arg.slice(15));
|
|
2025
|
+
if (!parsedWait) return {
|
|
2026
|
+
error: "--wait-receive requires accepted or handled",
|
|
2027
|
+
help: false,
|
|
2028
|
+
status
|
|
2029
|
+
};
|
|
2030
|
+
waitReceive = parsedWait;
|
|
2031
|
+
continue;
|
|
2032
|
+
}
|
|
2033
|
+
if (arg === "--wait-receive-text") {
|
|
2034
|
+
const value = argv[index + 1];
|
|
2035
|
+
if (!value) return {
|
|
2036
|
+
error: "--wait-receive-text requires text",
|
|
2037
|
+
help: false,
|
|
2038
|
+
status
|
|
2039
|
+
};
|
|
2040
|
+
waitReceiveText = value;
|
|
2041
|
+
index += 1;
|
|
2042
|
+
continue;
|
|
2043
|
+
}
|
|
2044
|
+
if (arg?.startsWith("--wait-receive-text=")) {
|
|
2045
|
+
waitReceiveText = arg.slice(20);
|
|
2046
|
+
if (!waitReceiveText) return {
|
|
2047
|
+
error: "--wait-receive-text requires text",
|
|
2048
|
+
help: false,
|
|
2049
|
+
status
|
|
2050
|
+
};
|
|
2051
|
+
continue;
|
|
2052
|
+
}
|
|
2053
|
+
if (arg === "--wait-receive-message-id") {
|
|
2054
|
+
const value = argv[index + 1];
|
|
2055
|
+
if (!value) return {
|
|
2056
|
+
error: "--wait-receive-message-id requires a message id",
|
|
2057
|
+
help: false,
|
|
2058
|
+
status
|
|
2059
|
+
};
|
|
2060
|
+
waitReceiveMessageId = value;
|
|
2061
|
+
index += 1;
|
|
2062
|
+
continue;
|
|
2063
|
+
}
|
|
2064
|
+
if (arg?.startsWith("--wait-receive-message-id=")) {
|
|
2065
|
+
waitReceiveMessageId = arg.slice(26);
|
|
2066
|
+
if (!waitReceiveMessageId) return {
|
|
2067
|
+
error: "--wait-receive-message-id requires a message id",
|
|
2068
|
+
help: false,
|
|
2069
|
+
status
|
|
2070
|
+
};
|
|
2071
|
+
continue;
|
|
2072
|
+
}
|
|
2073
|
+
if (arg === "--wait-receive-observed-after") {
|
|
2074
|
+
const value = parseIsoTimestampArgument(argv[index + 1]);
|
|
2075
|
+
if (!value) return {
|
|
2076
|
+
error: "--wait-receive-observed-after requires an ISO timestamp",
|
|
2077
|
+
help: false,
|
|
2078
|
+
status
|
|
2079
|
+
};
|
|
2080
|
+
waitReceiveObservedAfter = value;
|
|
2081
|
+
index += 1;
|
|
2082
|
+
continue;
|
|
2083
|
+
}
|
|
2084
|
+
if (arg?.startsWith("--wait-receive-observed-after=")) {
|
|
2085
|
+
const value = parseIsoTimestampArgument(arg.slice(30));
|
|
2086
|
+
if (!value) return {
|
|
2087
|
+
error: "--wait-receive-observed-after requires an ISO timestamp",
|
|
2088
|
+
help: false,
|
|
2089
|
+
status
|
|
2090
|
+
};
|
|
2091
|
+
waitReceiveObservedAfter = value;
|
|
2092
|
+
continue;
|
|
2093
|
+
}
|
|
2094
|
+
if (arg === "--wait-timeout-ms") {
|
|
2095
|
+
const parsedMs = parsePositiveIntegerArgument(argv[index + 1]);
|
|
2096
|
+
if (parsedMs === void 0) return {
|
|
2097
|
+
error: "--wait-timeout-ms requires a positive integer",
|
|
2098
|
+
help: false,
|
|
2099
|
+
status
|
|
2100
|
+
};
|
|
2101
|
+
waitTimeoutMs = parsedMs;
|
|
2102
|
+
index += 1;
|
|
2103
|
+
continue;
|
|
2104
|
+
}
|
|
2105
|
+
if (arg?.startsWith("--wait-timeout-ms=")) {
|
|
2106
|
+
const parsedMs = parsePositiveIntegerArgument(arg.slice(18));
|
|
2107
|
+
if (parsedMs === void 0) return {
|
|
2108
|
+
error: "--wait-timeout-ms requires a positive integer",
|
|
2109
|
+
help: false,
|
|
2110
|
+
status
|
|
2111
|
+
};
|
|
2112
|
+
waitTimeoutMs = parsedMs;
|
|
2113
|
+
continue;
|
|
2114
|
+
}
|
|
2115
|
+
if (arg === "--wait-poll-ms") {
|
|
2116
|
+
const parsedMs = parsePositiveIntegerArgument(argv[index + 1]);
|
|
2117
|
+
if (parsedMs === void 0) return {
|
|
2118
|
+
error: "--wait-poll-ms requires a positive integer",
|
|
2119
|
+
help: false,
|
|
2120
|
+
status
|
|
2121
|
+
};
|
|
2122
|
+
waitPollMs = parsedMs;
|
|
2123
|
+
index += 1;
|
|
2124
|
+
continue;
|
|
2125
|
+
}
|
|
2126
|
+
if (arg?.startsWith("--wait-poll-ms=")) {
|
|
2127
|
+
const parsedMs = parsePositiveIntegerArgument(arg.slice(15));
|
|
2128
|
+
if (parsedMs === void 0) return {
|
|
2129
|
+
error: "--wait-poll-ms requires a positive integer",
|
|
2130
|
+
help: false,
|
|
2131
|
+
status
|
|
2132
|
+
};
|
|
2133
|
+
waitPollMs = parsedMs;
|
|
2134
|
+
continue;
|
|
2135
|
+
}
|
|
2136
|
+
if (arg === "--bootstrap") {
|
|
2137
|
+
const value = argv[index + 1];
|
|
2138
|
+
if (!value) return {
|
|
2139
|
+
error: "--bootstrap requires a module specifier",
|
|
2140
|
+
help: false,
|
|
2141
|
+
status
|
|
2142
|
+
};
|
|
2143
|
+
bootstrap = value;
|
|
2144
|
+
index += 1;
|
|
2145
|
+
continue;
|
|
2146
|
+
}
|
|
2147
|
+
if (arg === "--manifest") {
|
|
2148
|
+
const value = argv[index + 1];
|
|
2149
|
+
if (!value) return {
|
|
2150
|
+
error: "--manifest requires a path",
|
|
2151
|
+
help: false,
|
|
2152
|
+
status
|
|
2153
|
+
};
|
|
2154
|
+
manifestPath = value;
|
|
2155
|
+
index += 1;
|
|
2156
|
+
continue;
|
|
2157
|
+
}
|
|
2158
|
+
if (arg?.startsWith("--manifest=")) {
|
|
2159
|
+
manifestPath = arg.slice(11);
|
|
2160
|
+
if (!manifestPath) return {
|
|
2161
|
+
error: "--manifest requires a path",
|
|
2162
|
+
help: false,
|
|
2163
|
+
status
|
|
2164
|
+
};
|
|
2165
|
+
continue;
|
|
2166
|
+
}
|
|
2167
|
+
if (arg?.startsWith("--bootstrap=")) {
|
|
2168
|
+
bootstrap = arg.slice(12);
|
|
2169
|
+
if (!bootstrap) return {
|
|
2170
|
+
error: "--bootstrap requires a module specifier",
|
|
2171
|
+
help: false,
|
|
2172
|
+
status
|
|
2173
|
+
};
|
|
2174
|
+
continue;
|
|
2175
|
+
}
|
|
2176
|
+
return {
|
|
2177
|
+
error: `Unknown argument: ${arg}`,
|
|
2178
|
+
help: false,
|
|
2179
|
+
status
|
|
2180
|
+
};
|
|
2181
|
+
}
|
|
2182
|
+
if (prompt !== void 0 && status) return {
|
|
2183
|
+
error: "--prompt cannot be combined with --status",
|
|
2184
|
+
help: false,
|
|
2185
|
+
status
|
|
2186
|
+
};
|
|
2187
|
+
if (checkConfig && status) return {
|
|
2188
|
+
error: "--check-config cannot be combined with --status",
|
|
2189
|
+
help: false,
|
|
2190
|
+
status,
|
|
2191
|
+
checkConfig
|
|
2192
|
+
};
|
|
2193
|
+
if (checkConfig && prompt !== void 0) return {
|
|
2194
|
+
error: "--check-config cannot be combined with --prompt",
|
|
2195
|
+
help: false,
|
|
2196
|
+
status,
|
|
2197
|
+
checkConfig
|
|
2198
|
+
};
|
|
2199
|
+
if (checkConfig && replayFeishuEventPath !== void 0) return {
|
|
2200
|
+
error: "--check-config cannot be combined with --replay-feishu-event",
|
|
2201
|
+
help: false,
|
|
2202
|
+
status,
|
|
2203
|
+
checkConfig
|
|
2204
|
+
};
|
|
2205
|
+
if (checkConfig && replayFeishuText !== void 0) return {
|
|
2206
|
+
error: "--check-config cannot be combined with --replay-feishu-text",
|
|
2207
|
+
help: false,
|
|
2208
|
+
status,
|
|
2209
|
+
checkConfig
|
|
2210
|
+
};
|
|
2211
|
+
if (checkConfig && printOpenClawEnvPath !== void 0) return {
|
|
2212
|
+
error: "--check-config cannot be combined with --print-openclaw-env",
|
|
2213
|
+
help: false,
|
|
2214
|
+
status,
|
|
2215
|
+
checkConfig
|
|
2216
|
+
};
|
|
2217
|
+
if (checkConfig && statusUrl) return {
|
|
2218
|
+
error: "--check-config cannot be combined with --status-url",
|
|
2219
|
+
help: false,
|
|
2220
|
+
status,
|
|
2221
|
+
checkConfig
|
|
2222
|
+
};
|
|
2223
|
+
if (prompt !== void 0 && statusUrl) return {
|
|
2224
|
+
error: "--prompt cannot be combined with --status-url",
|
|
2225
|
+
help: false,
|
|
2226
|
+
status
|
|
2227
|
+
};
|
|
2228
|
+
if (printOpenClawEnvPath !== void 0 && prompt !== void 0) return {
|
|
2229
|
+
error: "--print-openclaw-env cannot be combined with --prompt",
|
|
2230
|
+
help: false,
|
|
2231
|
+
status
|
|
2232
|
+
};
|
|
2233
|
+
if (printOpenClawEnvPath !== void 0 && replayFeishuEventPath !== void 0) return {
|
|
2234
|
+
error: "--print-openclaw-env cannot be combined with --replay-feishu-event",
|
|
2235
|
+
help: false,
|
|
2236
|
+
status
|
|
2237
|
+
};
|
|
2238
|
+
if (printOpenClawEnvPath !== void 0 && replayFeishuText !== void 0) return {
|
|
2239
|
+
error: "--print-openclaw-env cannot be combined with --replay-feishu-text",
|
|
2240
|
+
help: false,
|
|
2241
|
+
status
|
|
2242
|
+
};
|
|
2243
|
+
if (printOpenClawEnvPath !== void 0 && status) return {
|
|
2244
|
+
error: "--print-openclaw-env cannot be combined with --status",
|
|
2245
|
+
help: false,
|
|
2246
|
+
status
|
|
2247
|
+
};
|
|
2248
|
+
if (printOpenClawEnvPath !== void 0 && statusUrl) return {
|
|
2249
|
+
error: "--print-openclaw-env cannot be combined with --status-url",
|
|
2250
|
+
help: false,
|
|
2251
|
+
status
|
|
2252
|
+
};
|
|
2253
|
+
if (piApiKeyFile !== void 0 && printOpenClawEnvPath === void 0) return {
|
|
2254
|
+
error: "--pi-api-key-file requires --print-openclaw-env",
|
|
2255
|
+
help: false,
|
|
2256
|
+
status
|
|
2257
|
+
};
|
|
2258
|
+
if (replayFeishuEventPath !== void 0 && prompt !== void 0) return {
|
|
2259
|
+
error: "--replay-feishu-event cannot be combined with --prompt",
|
|
2260
|
+
help: false,
|
|
2261
|
+
status
|
|
2262
|
+
};
|
|
2263
|
+
if (replayFeishuEventPath !== void 0 && replayFeishuText !== void 0) return {
|
|
2264
|
+
error: "--replay-feishu-event cannot be combined with --replay-feishu-text",
|
|
2265
|
+
help: false,
|
|
2266
|
+
status
|
|
2267
|
+
};
|
|
2268
|
+
if (replayFeishuEventPath !== void 0 && status) return {
|
|
2269
|
+
error: "--replay-feishu-event cannot be combined with --status",
|
|
2270
|
+
help: false,
|
|
2271
|
+
status
|
|
2272
|
+
};
|
|
2273
|
+
if (replayFeishuEventPath !== void 0 && statusUrl) return {
|
|
2274
|
+
error: "--replay-feishu-event cannot be combined with --status-url",
|
|
2275
|
+
help: false,
|
|
2276
|
+
status
|
|
2277
|
+
};
|
|
2278
|
+
if (replayFeishuText !== void 0 && prompt !== void 0) return {
|
|
2279
|
+
error: "--replay-feishu-text cannot be combined with --prompt",
|
|
2280
|
+
help: false,
|
|
2281
|
+
status
|
|
2282
|
+
};
|
|
2283
|
+
if (replayFeishuText !== void 0 && status) return {
|
|
2284
|
+
error: "--replay-feishu-text cannot be combined with --status",
|
|
2285
|
+
help: false,
|
|
2286
|
+
status
|
|
2287
|
+
};
|
|
2288
|
+
if (replayFeishuText !== void 0 && statusUrl) return {
|
|
2289
|
+
error: "--replay-feishu-text cannot be combined with --status-url",
|
|
2290
|
+
help: false,
|
|
2291
|
+
status
|
|
2292
|
+
};
|
|
2293
|
+
if (feishuReplayMessageId !== void 0 && replayFeishuText === void 0) return {
|
|
2294
|
+
error: "--feishu-message-id requires --replay-feishu-text",
|
|
2295
|
+
help: false,
|
|
2296
|
+
status
|
|
2297
|
+
};
|
|
2298
|
+
if (feishuReplayChatId !== void 0 && replayFeishuText === void 0) return {
|
|
2299
|
+
error: "--feishu-chat-id requires --replay-feishu-text",
|
|
2300
|
+
help: false,
|
|
2301
|
+
status
|
|
2302
|
+
};
|
|
2303
|
+
if (feishuReplayThreadId !== void 0 && replayFeishuText === void 0) return {
|
|
2304
|
+
error: "--feishu-thread-id requires --replay-feishu-text",
|
|
2305
|
+
help: false,
|
|
2306
|
+
status
|
|
2307
|
+
};
|
|
2308
|
+
if (feishuReplayTenantKey !== void 0 && replayFeishuText === void 0) return {
|
|
2309
|
+
error: "--feishu-tenant-key requires --replay-feishu-text",
|
|
2310
|
+
help: false,
|
|
2311
|
+
status
|
|
2312
|
+
};
|
|
2313
|
+
if (sessionKey !== void 0 && prompt === void 0) return {
|
|
2314
|
+
error: "--session-key requires --prompt",
|
|
2315
|
+
help: false,
|
|
2316
|
+
status
|
|
2317
|
+
};
|
|
2318
|
+
if (waitReceive !== void 0 && status) return {
|
|
2319
|
+
error: "--wait-receive cannot be combined with --status",
|
|
2320
|
+
help: false,
|
|
2321
|
+
status
|
|
2322
|
+
};
|
|
2323
|
+
if (waitReceive !== void 0 && checkConfig) return {
|
|
2324
|
+
error: "--wait-receive cannot be combined with --check-config",
|
|
2325
|
+
help: false,
|
|
2326
|
+
status,
|
|
2327
|
+
checkConfig
|
|
2328
|
+
};
|
|
2329
|
+
if (waitReceive !== void 0 && prompt !== void 0) return {
|
|
2330
|
+
error: "--wait-receive cannot be combined with --prompt",
|
|
2331
|
+
help: false,
|
|
2332
|
+
status
|
|
2333
|
+
};
|
|
2334
|
+
if (waitReceive !== void 0 && replayFeishuEventPath !== void 0) return {
|
|
2335
|
+
error: "--wait-receive cannot be combined with --replay-feishu-event",
|
|
2336
|
+
help: false,
|
|
2337
|
+
status
|
|
2338
|
+
};
|
|
2339
|
+
if (waitReceive !== void 0 && replayFeishuText !== void 0) return {
|
|
2340
|
+
error: "--wait-receive cannot be combined with --replay-feishu-text",
|
|
2341
|
+
help: false,
|
|
2342
|
+
status
|
|
2343
|
+
};
|
|
2344
|
+
if (waitReceive !== void 0 && printOpenClawEnvPath !== void 0) return {
|
|
2345
|
+
error: "--wait-receive cannot be combined with --print-openclaw-env",
|
|
2346
|
+
help: false,
|
|
2347
|
+
status
|
|
2348
|
+
};
|
|
2349
|
+
if (waitTimeoutMs !== void 0 && waitReceive === void 0) return {
|
|
2350
|
+
error: "--wait-timeout-ms requires --wait-receive",
|
|
2351
|
+
help: false,
|
|
2352
|
+
status
|
|
2353
|
+
};
|
|
2354
|
+
if (waitPollMs !== void 0 && waitReceive === void 0) return {
|
|
2355
|
+
error: "--wait-poll-ms requires --wait-receive",
|
|
2356
|
+
help: false,
|
|
2357
|
+
status
|
|
2358
|
+
};
|
|
2359
|
+
if (waitReceiveText !== void 0 && waitReceive !== "handled") return {
|
|
2360
|
+
error: "--wait-receive-text requires --wait-receive handled",
|
|
2361
|
+
help: false,
|
|
2362
|
+
status
|
|
2363
|
+
};
|
|
2364
|
+
if (waitReceiveMessageId !== void 0 && waitReceive === void 0) return {
|
|
2365
|
+
error: "--wait-receive-message-id requires --wait-receive",
|
|
2366
|
+
help: false,
|
|
2367
|
+
status
|
|
2368
|
+
};
|
|
2369
|
+
if (waitReceiveObservedAfter !== void 0 && waitReceive === void 0) return {
|
|
2370
|
+
error: "--wait-receive-observed-after requires --wait-receive",
|
|
2371
|
+
help: false,
|
|
2372
|
+
status
|
|
2373
|
+
};
|
|
2374
|
+
const recovery = recoveryCli.build();
|
|
2375
|
+
if (recovery.error) return {
|
|
2376
|
+
error: recovery.error,
|
|
2377
|
+
help: false,
|
|
2378
|
+
status
|
|
2379
|
+
};
|
|
2380
|
+
if (recovery.command) {
|
|
2381
|
+
if (!manifestPath) return {
|
|
2382
|
+
error: "Recovery commands require --manifest",
|
|
2383
|
+
help: false,
|
|
2384
|
+
status
|
|
2385
|
+
};
|
|
2386
|
+
if (checkConfig || prompt !== void 0 || printOpenClawEnvPath !== void 0 || replayFeishuEventPath !== void 0 || replayFeishuText !== void 0 || status || statusUrl !== void 0 || waitReceive !== void 0) return {
|
|
2387
|
+
error: "Recovery commands cannot be combined with another one-shot command",
|
|
2388
|
+
help: false,
|
|
2389
|
+
status
|
|
2390
|
+
};
|
|
2391
|
+
}
|
|
2392
|
+
return {
|
|
2393
|
+
...bootstrap ? { bootstrap } : {},
|
|
2394
|
+
checkConfig,
|
|
2395
|
+
...envFilePath ? { envFilePath } : {},
|
|
2396
|
+
...feishuReplayChatId !== void 0 ? { feishuReplayChatId } : {},
|
|
2397
|
+
...feishuReplayMessageId !== void 0 ? { feishuReplayMessageId } : {},
|
|
2398
|
+
...feishuReplayTenantKey !== void 0 ? { feishuReplayTenantKey } : {},
|
|
2399
|
+
...feishuReplayThreadId !== void 0 ? { feishuReplayThreadId } : {},
|
|
2400
|
+
help: false,
|
|
2401
|
+
...manifestPath ? { manifestPath } : {},
|
|
2402
|
+
...piApiKeyFile !== void 0 ? { piApiKeyFile } : {},
|
|
2403
|
+
...prompt !== void 0 ? { prompt } : {},
|
|
2404
|
+
...printOpenClawEnvPath !== void 0 ? { printOpenClawEnvPath } : {},
|
|
2405
|
+
...replayFeishuEventPath !== void 0 ? { replayFeishuEventPath } : {},
|
|
2406
|
+
...replayFeishuText !== void 0 ? { replayFeishuText } : {},
|
|
2407
|
+
...recovery.command ? { recoveryCommand: recovery.command } : {},
|
|
2408
|
+
...sessionKey ? { sessionKey } : {},
|
|
2409
|
+
status,
|
|
2410
|
+
...statusUrl ? { statusUrl } : {},
|
|
2411
|
+
...waitPollMs !== void 0 ? { waitPollMs } : {},
|
|
2412
|
+
...waitReceive !== void 0 ? { waitReceive } : {},
|
|
2413
|
+
...waitReceiveMessageId !== void 0 ? { waitReceiveMessageId } : {},
|
|
2414
|
+
...waitReceiveObservedAfter !== void 0 ? { waitReceiveObservedAfter } : {},
|
|
2415
|
+
...waitReceiveText !== void 0 ? { waitReceiveText } : {},
|
|
2416
|
+
...waitTimeoutMs !== void 0 ? { waitTimeoutMs } : {}
|
|
2417
|
+
};
|
|
2418
|
+
}
|
|
2419
|
+
function toRedactedConfig(config) {
|
|
2420
|
+
return {
|
|
2421
|
+
agentId: config.agentId,
|
|
2422
|
+
feishu: {
|
|
2423
|
+
appIdPresent: Boolean(config.feishu.appId),
|
|
2424
|
+
appSecretPresent: Boolean(config.feishu.appSecret),
|
|
2425
|
+
baseUrl: config.feishu.baseUrl,
|
|
2426
|
+
streamMinIntervalMs: config.feishu.streamMinIntervalMs
|
|
2427
|
+
},
|
|
2428
|
+
pi: {
|
|
2429
|
+
apiKeyPresent: Boolean(config.pi.apiKey),
|
|
2430
|
+
...config.pi.baseUrl ? { baseUrl: config.pi.baseUrl } : {},
|
|
2431
|
+
...config.pi.model ? { model: config.pi.model } : {},
|
|
2432
|
+
...config.pi.thinkingLevel ? { thinkingLevel: config.pi.thinkingLevel } : {}
|
|
2433
|
+
}
|
|
2434
|
+
};
|
|
2435
|
+
}
|
|
2436
|
+
function toRedactedDeploymentManifest(manifest) {
|
|
2437
|
+
return {
|
|
2438
|
+
agents: manifest.agents.map(({ agentId, endpointIds, pluginId, profileId }) => ({
|
|
2439
|
+
agentId,
|
|
2440
|
+
endpointIds,
|
|
2441
|
+
pluginId,
|
|
2442
|
+
profileId
|
|
2443
|
+
})),
|
|
2444
|
+
automations: (manifest.automations ?? []).map(({ agentId, delivery, enabled, id, required, schedule, templateId, timeZone }) => ({
|
|
2445
|
+
agentId,
|
|
2446
|
+
delivery: {
|
|
2447
|
+
endpointId: delivery.endpointId,
|
|
2448
|
+
targetRef: delivery.targetRef,
|
|
2449
|
+
targetType: delivery.targetType
|
|
2450
|
+
},
|
|
2451
|
+
enabled,
|
|
2452
|
+
id,
|
|
2453
|
+
required,
|
|
2454
|
+
schedule,
|
|
2455
|
+
templateId,
|
|
2456
|
+
timeZone
|
|
2457
|
+
})),
|
|
2458
|
+
defaultAgentId: manifest.defaultAgentId,
|
|
2459
|
+
defaultEndpointId: manifest.defaultEndpointId,
|
|
2460
|
+
endpoints: manifest.endpoints.map(({ agentId, credentialRef, enabled, id, required, sessionNamespace }) => ({
|
|
2461
|
+
agentId,
|
|
2462
|
+
credentialRef,
|
|
2463
|
+
enabled,
|
|
2464
|
+
id,
|
|
2465
|
+
required,
|
|
2466
|
+
sessionNamespace
|
|
2467
|
+
})),
|
|
2468
|
+
plugins: manifest.plugins.map(({ id, module, required }) => ({
|
|
2469
|
+
id,
|
|
2470
|
+
module,
|
|
2471
|
+
required
|
|
2472
|
+
}))
|
|
2473
|
+
};
|
|
2474
|
+
}
|
|
2475
|
+
async function loadCliEnv(envFilePath, overrideEnv) {
|
|
2476
|
+
if (!envFilePath) return overrideEnv;
|
|
2477
|
+
return loadMergedLocalEnvFile(envFilePath, overrideEnv);
|
|
2478
|
+
}
|
|
2479
|
+
async function readFeishuReceiveMessagePayload(filePath) {
|
|
2480
|
+
return JSON.parse(await readFile(filePath, "utf8"));
|
|
2481
|
+
}
|
|
2482
|
+
function createSyntheticFeishuTextReplayPayload(text, options) {
|
|
2483
|
+
return { event: {
|
|
2484
|
+
message: {
|
|
2485
|
+
chat_id: options.chatId ?? "oc_cli",
|
|
2486
|
+
content: JSON.stringify({ text }),
|
|
2487
|
+
message_id: options.messageId ?? `om_cli_${Date.now()}`,
|
|
2488
|
+
message_type: "text",
|
|
2489
|
+
thread_id: options.threadId ?? "omt_cli"
|
|
2490
|
+
},
|
|
2491
|
+
sender: { tenant_key: options.tenantKey ?? "tenant_cli" }
|
|
2492
|
+
} };
|
|
2493
|
+
}
|
|
2494
|
+
function formatCliError(error) {
|
|
2495
|
+
if (error instanceof ReceiveWaitTimeoutError) return `${error.message}\nLast status:\n${JSON.stringify(error.lastStatus, null, 2)}`;
|
|
2496
|
+
return error instanceof Error ? error.message : String(error);
|
|
2497
|
+
}
|
|
2498
|
+
var ReceiveWaitTimeoutError = class extends Error {
|
|
2499
|
+
target;
|
|
2500
|
+
lastStatus;
|
|
2501
|
+
messageId;
|
|
2502
|
+
observedAfter;
|
|
2503
|
+
text;
|
|
2504
|
+
constructor(target, lastStatus, messageId, observedAfter, text) {
|
|
2505
|
+
super(`Timed out waiting for ${target === "accepted" ? "receive.lastAccepted" : "receive.lastHandled"}${messageId ? ` with message id ${JSON.stringify(messageId)}` : ""}${observedAfter ? ` observed after ${JSON.stringify(observedAfter)}` : ""}${text ? ` containing text ${JSON.stringify(text)}` : ""}`);
|
|
2506
|
+
this.target = target;
|
|
2507
|
+
this.lastStatus = lastStatus;
|
|
2508
|
+
this.messageId = messageId;
|
|
2509
|
+
this.observedAfter = observedAfter;
|
|
2510
|
+
this.text = text;
|
|
2511
|
+
this.name = "ReceiveWaitTimeoutError";
|
|
2512
|
+
}
|
|
2513
|
+
};
|
|
2514
|
+
async function fetchLiveStatus(url) {
|
|
2515
|
+
const response = await fetch(url, { headers: { accept: "application/json" } });
|
|
2516
|
+
if (!response.ok) throw new Error(`Status request failed with HTTP ${response.status}`);
|
|
2517
|
+
return response.json();
|
|
2518
|
+
}
|
|
2519
|
+
async function waitForLiveReceiveStatus(url, target, options) {
|
|
2520
|
+
return waitForReceiveStatus(() => fetchLiveStatus(url), target, options);
|
|
2521
|
+
}
|
|
2522
|
+
async function waitForReceiveStatus(readStatus, target, options) {
|
|
2523
|
+
const startedAt = Date.now();
|
|
2524
|
+
let lastStatus;
|
|
2525
|
+
while (true) {
|
|
2526
|
+
const status = await readStatus();
|
|
2527
|
+
lastStatus = status;
|
|
2528
|
+
if (hasReceiveObservation(status, target, {
|
|
2529
|
+
...options.messageId ? { messageId: options.messageId } : {},
|
|
2530
|
+
...options.observedAfter ? { observedAfter: options.observedAfter } : {},
|
|
2531
|
+
...options.text ? { text: options.text } : {}
|
|
2532
|
+
})) return status;
|
|
2533
|
+
if (Date.now() - startedAt >= options.timeoutMs) throw new ReceiveWaitTimeoutError(target, lastStatus, options.messageId, options.observedAfter, options.text);
|
|
2534
|
+
await sleep(options.pollMs);
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
function hasReceiveObservation(status, target, criteria) {
|
|
2538
|
+
const receive = readRecord(status)?.receive;
|
|
2539
|
+
const receiveRecord = readRecord(receive);
|
|
2540
|
+
if (target === "accepted") {
|
|
2541
|
+
const lastAccepted = readRecord(receiveRecord?.lastAccepted);
|
|
2542
|
+
if (!lastAccepted) return false;
|
|
2543
|
+
if (criteria.messageId === void 0) return observedAfterMatches(lastAccepted, criteria.observedAfter);
|
|
2544
|
+
return readRecord(lastAccepted.message)?.messageId === criteria.messageId && observedAfterMatches(lastAccepted, criteria.observedAfter);
|
|
2545
|
+
}
|
|
2546
|
+
const lastHandled = readRecord(receiveRecord?.lastHandled);
|
|
2547
|
+
if (!lastHandled) return false;
|
|
2548
|
+
if (criteria.messageId !== void 0 && lastHandled.messageId !== criteria.messageId) return false;
|
|
2549
|
+
if (!observedAfterMatches(lastHandled, criteria.observedAfter)) return false;
|
|
2550
|
+
if (criteria.text === void 0) return true;
|
|
2551
|
+
const intake = readRecord(lastHandled.intake);
|
|
2552
|
+
return typeof intake?.text === "string" && intake.text.includes(criteria.text);
|
|
2553
|
+
}
|
|
2554
|
+
function readRecord(value) {
|
|
2555
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
2556
|
+
}
|
|
2557
|
+
function observedAfterMatches(observation, observedAfter) {
|
|
2558
|
+
if (observedAfter === void 0) return true;
|
|
2559
|
+
const observedAtMs = readObservedAtMs(observation.observedAt);
|
|
2560
|
+
return observedAtMs !== void 0 && observedAtMs > Date.parse(observedAfter);
|
|
2561
|
+
}
|
|
2562
|
+
function readObservedAtMs(value) {
|
|
2563
|
+
if (value instanceof Date) {
|
|
2564
|
+
const time = value.getTime();
|
|
2565
|
+
return Number.isNaN(time) ? void 0 : time;
|
|
2566
|
+
}
|
|
2567
|
+
if (typeof value === "string") {
|
|
2568
|
+
const time = Date.parse(value);
|
|
2569
|
+
return Number.isNaN(time) ? void 0 : time;
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
function sleep(ms) {
|
|
2573
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2574
|
+
}
|
|
2575
|
+
function parseWaitReceive(value) {
|
|
2576
|
+
return value === "accepted" || value === "handled" ? value : void 0;
|
|
2577
|
+
}
|
|
2578
|
+
function parsePositiveIntegerArgument(value) {
|
|
2579
|
+
return value && /^[1-9]\d*$/.test(value) ? Number(value) : void 0;
|
|
2580
|
+
}
|
|
2581
|
+
function parseIsoTimestampArgument(value) {
|
|
2582
|
+
if (!value) return;
|
|
2583
|
+
const time = Date.parse(value);
|
|
2584
|
+
return Number.isNaN(time) ? void 0 : new Date(time).toISOString();
|
|
2585
|
+
}
|
|
2586
|
+
function hasStatusReporter(daemon) {
|
|
2587
|
+
return typeof daemon.status === "function";
|
|
2588
|
+
}
|
|
2589
|
+
function hasPromptRunner(daemon) {
|
|
2590
|
+
return typeof daemon.promptText === "function";
|
|
2591
|
+
}
|
|
2592
|
+
function hasFeishuReplayRunner(daemon) {
|
|
2593
|
+
return typeof daemon.replayReceiveMessage === "function";
|
|
2594
|
+
}
|
|
2595
|
+
function hasRecoveryRunner(daemon) {
|
|
2596
|
+
return typeof daemon.openRecoveryControl === "function";
|
|
2597
|
+
}
|
|
2598
|
+
//#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 };
|