@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.
@@ -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 };