@rivus/agent 0.13.2 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -18
- package/dist/acp.d.ts +40 -40
- package/dist/acp.js +71 -31
- package/dist/bootstrap/pi-feishu.d.ts +20 -0
- package/dist/bootstrap/pi-feishu.js +596 -0
- package/dist/{agent-loop.d.ts → chunks/agent-loop.d.ts} +293 -44
- package/dist/chunks/agent-loop.js +1272 -0
- package/dist/chunks/api.d.ts +70 -0
- package/dist/chunks/api.js +471 -0
- package/dist/{rivus-plugin.d.ts → chunks/api2.d.ts} +271 -120
- package/dist/chunks/api2.js +1331 -0
- package/dist/chunks/api3.d.ts +402 -0
- package/dist/chunks/index.d.ts +3662 -0
- package/dist/chunks/module.js +267 -0
- package/dist/chunks/pi-skill-tool.js +460 -0
- package/dist/chunks/pi-tool-proxy.d.ts +188 -0
- package/dist/chunks/pi.js +329 -0
- package/dist/{rivus-daemon-cli.js → chunks/rivus-daemon-cli.js} +2197 -1526
- package/dist/{rivus-plugin-testkit.d.ts → chunks/rivus-plugin-testkit.d.ts} +1 -1
- package/dist/{rivus-plugin-testkit.js → chunks/rivus-plugin-testkit.js} +11 -3
- package/dist/chunks/sha256-digest.js +12 -0
- package/dist/chunks/spi.d.ts +1 -0
- package/dist/chunks/spi.js +2 -0
- package/dist/chunks/src.js +9897 -0
- package/dist/cli.js +604 -95
- package/dist/index.d.ts +8 -3645
- package/dist/index.js +9 -10483
- package/dist/mcp.d.ts +3 -38
- package/dist/mcp.js +4 -114
- package/dist/pi.d.ts +95 -9
- package/dist/pi.js +3 -146
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +1 -1
- package/examples/pi-feishu-deployment.bootstrap.ts +45 -54
- package/examples/pi-feishu.bootstrap.ts +53 -37
- package/examples/rivus-starter.plugin.mjs +3 -1
- package/package.json +12 -14
- package/dist/agent-loop.js +0 -121
- package/dist/agent-memory.d.ts +0 -100
- package/dist/agent-memory.js +0 -114
- package/dist/background-session-authority.js +0 -224
- package/dist/background-session-input.js +0 -45
- package/dist/background-session-service.d.ts +0 -291
- package/dist/pi-tool-proxy.d.ts +0 -197
- package/dist/rivus-plugin-registry.js +0 -215
- package/dist/tool-input-digest.js +0 -128
|
@@ -1,328 +1,18 @@
|
|
|
1
|
-
import { t as MEMORY_SCOPES } from "./
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { a as deepFreeze, f as isRivusRuntimeToolId, t as restrictRivusAgentDefinitionGrants, v as MEMORY_SCOPES } from "./api.js";
|
|
2
|
+
import { t as createSha256Digest } from "./sha256-digest.js";
|
|
3
|
+
import { X as narrowBackgroundSessionDefinition } from "./api2.js";
|
|
4
|
+
import { n as createRivusHostToolDescriptorProvider, t as createRivusAgentCatalog } from "./module.js";
|
|
4
5
|
import { createRequire } from "node:module";
|
|
5
|
-
import { Effect } from "effect";
|
|
6
|
+
import { Cause, Deferred, Effect, Either, Exit, Option } from "effect";
|
|
6
7
|
import { createHash, randomUUID } from "node:crypto";
|
|
7
8
|
import { lstat, open, readFile, readdir, realpath, stat } from "node:fs/promises";
|
|
8
|
-
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
9
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
9
10
|
import { pathToFileURL } from "node:url";
|
|
10
11
|
import { constants } from "node:fs";
|
|
11
|
-
//#region src/
|
|
12
|
-
|
|
13
|
-
const toolIds = Object.freeze([...new Set(requestedToolIds)].sort());
|
|
14
|
-
const toolsById = new Map(definition.tools.map((tool) => [tool.id, tool]));
|
|
15
|
-
const tools = Object.freeze(toolIds.map((toolId) => {
|
|
16
|
-
const tool = toolsById.get(toolId);
|
|
17
|
-
if (!tool) throw new Error(`Automation Runtime requests ungranted tool: ${toolId}`);
|
|
18
|
-
return tool;
|
|
19
|
-
}));
|
|
20
|
-
const skillIds = Object.freeze([...new Set(requestedSkillIds)].sort());
|
|
21
|
-
const skillsById = new Map(definition.skills.map((skill) => [skill.id, skill]));
|
|
22
|
-
const skills = Object.freeze(skillIds.map((skillId) => {
|
|
23
|
-
const skill = skillsById.get(skillId);
|
|
24
|
-
if (!skill) throw new Error(`Automation Runtime requests ungranted skill: ${skillId}`);
|
|
25
|
-
return skill;
|
|
26
|
-
}));
|
|
27
|
-
return deepFreeze({
|
|
28
|
-
...definition,
|
|
29
|
-
memory: {
|
|
30
|
-
scopes: [],
|
|
31
|
-
tool: false
|
|
32
|
-
},
|
|
33
|
-
skillGrantSet: {
|
|
34
|
-
revision: createHash("sha256").update(JSON.stringify({
|
|
35
|
-
parentRevision: definition.skillGrantSet.revision,
|
|
36
|
-
skillIds
|
|
37
|
-
})).digest("hex"),
|
|
38
|
-
skillIds
|
|
39
|
-
},
|
|
40
|
-
skills,
|
|
41
|
-
toolGrantSet: {
|
|
42
|
-
revision: createHash("sha256").update(JSON.stringify({
|
|
43
|
-
parentRevision: definition.toolGrantSet.revision,
|
|
44
|
-
toolIds
|
|
45
|
-
})).digest("hex"),
|
|
46
|
-
toolIds
|
|
47
|
-
},
|
|
48
|
-
tools
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
//#endregion
|
|
52
|
-
//#region src/application/plugin/rivus-plugin-loader.ts
|
|
53
|
-
var RivusPluginLoadError = class extends Error {
|
|
54
|
-
pluginId;
|
|
55
|
-
moduleSpecifier;
|
|
56
|
-
name = "RivusPluginLoadError";
|
|
57
|
-
constructor(pluginId, moduleSpecifier, message, options) {
|
|
58
|
-
super(message, options);
|
|
59
|
-
this.pluginId = pluginId;
|
|
60
|
-
this.moduleSpecifier = moduleSpecifier;
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
async function loadRivusDeployment(options) {
|
|
64
|
-
validateRivusDeploymentManifest(options.manifest);
|
|
65
|
-
const catalog = createRivusPluginCatalog();
|
|
66
|
-
const pluginStatuses = [];
|
|
67
|
-
const statusByPlugin = /* @__PURE__ */ new Map();
|
|
68
|
-
for (const declaration of options.manifest.plugins) try {
|
|
69
|
-
const plugin = await resolvePluginExport(await options.loadModule({
|
|
70
|
-
deploymentRoot: options.deploymentRoot,
|
|
71
|
-
module: declaration.module,
|
|
72
|
-
pluginId: declaration.id
|
|
73
|
-
}));
|
|
74
|
-
if (plugin.manifest.id !== declaration.id) throw new Error(`plugin manifest id ${plugin.manifest.id} does not match declaration ${declaration.id}`);
|
|
75
|
-
catalog.registerPlugin(plugin);
|
|
76
|
-
const status = Object.freeze({
|
|
77
|
-
id: declaration.id,
|
|
78
|
-
module: declaration.module,
|
|
79
|
-
required: declaration.required,
|
|
80
|
-
status: "loaded",
|
|
81
|
-
version: plugin.manifest.version
|
|
82
|
-
});
|
|
83
|
-
pluginStatuses.push(status);
|
|
84
|
-
statusByPlugin.set(declaration.id, status);
|
|
85
|
-
} catch (cause) {
|
|
86
|
-
const message = cause instanceof Error ? cause.message : String(cause);
|
|
87
|
-
if (declaration.required) throw new RivusPluginLoadError(declaration.id, declaration.module, `required plugin ${declaration.id} failed to load: ${message}`, { cause });
|
|
88
|
-
const status = Object.freeze({
|
|
89
|
-
error: message,
|
|
90
|
-
id: declaration.id,
|
|
91
|
-
module: declaration.module,
|
|
92
|
-
required: false,
|
|
93
|
-
status: "failed"
|
|
94
|
-
});
|
|
95
|
-
pluginStatuses.push(status);
|
|
96
|
-
statusByPlugin.set(declaration.id, status);
|
|
97
|
-
}
|
|
98
|
-
const agentStatuses = [];
|
|
99
|
-
const definitions = [];
|
|
100
|
-
for (const agent of options.manifest.agents) {
|
|
101
|
-
const pluginStatus = statusByPlugin.get(agent.pluginId);
|
|
102
|
-
if (pluginStatus.status === "failed") {
|
|
103
|
-
agentStatuses.push(Object.freeze({
|
|
104
|
-
agentId: agent.agentId,
|
|
105
|
-
pluginId: agent.pluginId,
|
|
106
|
-
profileId: agent.profileId,
|
|
107
|
-
reason: `plugin ${agent.pluginId} is unavailable: ${pluginStatus.error}`,
|
|
108
|
-
status: "disabled"
|
|
109
|
-
}));
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
try {
|
|
113
|
-
const baseDefinition = resolveRivusAgentDefinition(catalog, agent, { backgroundSessions: options.manifest.backgroundSessions?.enabled === true });
|
|
114
|
-
const projectSpace = agent.projectSpaceId ? options.manifest.projectSpaces?.find(({ id }) => id === agent.projectSpaceId) : void 0;
|
|
115
|
-
const definition = projectSpace ? deepFreeze({
|
|
116
|
-
...baseDefinition,
|
|
117
|
-
projectSpaceRevision: `project-space-declaration:${JSON.stringify(projectSpace)}`
|
|
118
|
-
}) : baseDefinition;
|
|
119
|
-
definitions.push(definition);
|
|
120
|
-
agentStatuses.push(Object.freeze({
|
|
121
|
-
agentId: agent.agentId,
|
|
122
|
-
definition,
|
|
123
|
-
pluginId: agent.pluginId,
|
|
124
|
-
profileId: agent.profileId,
|
|
125
|
-
status: "enabled"
|
|
126
|
-
}));
|
|
127
|
-
} catch (cause) {
|
|
128
|
-
const declaration = options.manifest.plugins.find(({ id }) => id === agent.pluginId);
|
|
129
|
-
const message = cause instanceof Error ? cause.message : String(cause);
|
|
130
|
-
if (declaration.required) throw new RivusPluginLoadError(declaration.id, declaration.module, `deployment ${agent.agentId} failed to resolve: ${message}`, { cause });
|
|
131
|
-
agentStatuses.push(Object.freeze({
|
|
132
|
-
agentId: agent.agentId,
|
|
133
|
-
pluginId: agent.pluginId,
|
|
134
|
-
profileId: agent.profileId,
|
|
135
|
-
reason: message,
|
|
136
|
-
status: "disabled"
|
|
137
|
-
}));
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
const agentStatusById = new Map(agentStatuses.map((agent) => [agent.agentId, agent]));
|
|
141
|
-
const automationTemplates = new Map(catalog.snapshot().automations.map((template) => [template.id, template]));
|
|
142
|
-
const automationDefinitions = [];
|
|
143
|
-
for (const automation of options.manifest.automations ?? []) {
|
|
144
|
-
const agent = agentStatusById.get(automation.agentId);
|
|
145
|
-
if (!agent || agent.status === "disabled" || !agent.definition) continue;
|
|
146
|
-
const deliveryEndpoint = options.manifest.endpoints.find(({ id }) => id === automation.delivery.endpointId);
|
|
147
|
-
const presentationAgent = agentStatusById.get(deliveryEndpoint.agentId);
|
|
148
|
-
if (!presentationAgent || presentationAgent.status === "disabled" || !presentationAgent.definition) continue;
|
|
149
|
-
const template = automationTemplates.get(automation.templateId);
|
|
150
|
-
if (!template) throw new Error(`automation ${automation.id} references unknown template: ${automation.templateId}`);
|
|
151
|
-
if (template.pluginId !== agent.pluginId || template.profileId !== agent.profileId) throw new Error(`automation ${automation.id} template is not owned by agent profile ${agent.profileId}`);
|
|
152
|
-
automationDefinitions.push(deepFreeze({
|
|
153
|
-
...automation,
|
|
154
|
-
runtimeDefinition: resolveRivusAutomationRuntimeDefinition(agent.definition, template.requestedToolIds, template.requestedSkillIds),
|
|
155
|
-
template
|
|
156
|
-
}));
|
|
157
|
-
}
|
|
158
|
-
return Object.freeze({
|
|
159
|
-
agents: Object.freeze(agentStatuses),
|
|
160
|
-
automationDefinitions: Object.freeze(automationDefinitions),
|
|
161
|
-
catalog,
|
|
162
|
-
definitions: Object.freeze(definitions),
|
|
163
|
-
manifest: options.manifest,
|
|
164
|
-
plugins: Object.freeze(pluginStatuses)
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
function validateRivusDeploymentManifest(manifest) {
|
|
168
|
-
const projectSpaceIds = /* @__PURE__ */ new Set();
|
|
169
|
-
for (const projectSpace of manifest.projectSpaces ?? []) {
|
|
170
|
-
if (projectSpaceIds.has(projectSpace.id)) throw new Error(`duplicate project space: ${projectSpace.id}`);
|
|
171
|
-
projectSpaceIds.add(projectSpace.id);
|
|
172
|
-
validateRelativeProjectPath(projectSpace.root, `project space ${projectSpace.id} root`);
|
|
173
|
-
validateRelativeProjectPath(projectSpace.workingDirectory, `project space ${projectSpace.id} working directory`);
|
|
174
|
-
if (projectSpace.skills.sources.length === 0) throw new Error(`project space ${projectSpace.id} must declare at least one Skill source`);
|
|
175
|
-
const sources = /* @__PURE__ */ new Set();
|
|
176
|
-
for (const source of projectSpace.skills.sources) {
|
|
177
|
-
validateRelativeProjectPath(source, `project space ${projectSpace.id} Skill source`);
|
|
178
|
-
if (sources.has(source)) throw new Error(`project space ${projectSpace.id} contains duplicate Skill source: ${source}`);
|
|
179
|
-
sources.add(source);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
const pluginIds = /* @__PURE__ */ new Set();
|
|
183
|
-
for (const plugin of manifest.plugins) {
|
|
184
|
-
if (pluginIds.has(plugin.id)) throw new Error(`duplicate plugin declaration: ${plugin.id}`);
|
|
185
|
-
validateModuleSpecifier(plugin.module);
|
|
186
|
-
pluginIds.add(plugin.id);
|
|
187
|
-
}
|
|
188
|
-
const agentIds = /* @__PURE__ */ new Set();
|
|
189
|
-
const agentById = /* @__PURE__ */ new Map();
|
|
190
|
-
for (const agent of manifest.agents) {
|
|
191
|
-
if (agentIds.has(agent.agentId)) throw new Error(`duplicate agent deployment: ${agent.agentId}`);
|
|
192
|
-
agentIds.add(agent.agentId);
|
|
193
|
-
if (!pluginIds.has(agent.pluginId)) throw new Error(`agent ${agent.agentId} references undeclared plugin: ${agent.pluginId}`);
|
|
194
|
-
if (agent.projectSpaceId && !projectSpaceIds.has(agent.projectSpaceId)) throw new Error(`agent ${agent.agentId} references unknown project space: ${agent.projectSpaceId}`);
|
|
195
|
-
agentById.set(agent.agentId, agent);
|
|
196
|
-
}
|
|
197
|
-
const automationIds = /* @__PURE__ */ new Set();
|
|
198
|
-
for (const automation of manifest.automations ?? []) {
|
|
199
|
-
if (automationIds.has(automation.id)) throw new Error(`duplicate automation binding: ${automation.id}`);
|
|
200
|
-
automationIds.add(automation.id);
|
|
201
|
-
if (!agentById.get(automation.agentId)) throw new Error(`automation ${automation.id} references unknown agent: ${automation.agentId}`);
|
|
202
|
-
const endpoint = manifest.endpoints.find(({ id }) => id === automation.delivery.endpointId);
|
|
203
|
-
if (!endpoint) throw new Error(`automation ${automation.id} references unknown delivery endpoint: ${automation.delivery.endpointId}`);
|
|
204
|
-
if (automation.enabled && !endpoint.enabled) throw new Error(`automation ${automation.id} delivery endpoint must be enabled`);
|
|
205
|
-
}
|
|
206
|
-
const endpointIds = /* @__PURE__ */ new Set();
|
|
207
|
-
const sessionNamespaces = /* @__PURE__ */ new Set();
|
|
208
|
-
for (const endpoint of manifest.endpoints) {
|
|
209
|
-
if (endpointIds.has(endpoint.id)) throw new Error(`duplicate endpoint binding: ${endpoint.id}`);
|
|
210
|
-
endpointIds.add(endpoint.id);
|
|
211
|
-
if (sessionNamespaces.has(endpoint.sessionNamespace)) throw new Error(`duplicate endpoint session namespace: ${endpoint.sessionNamespace}`);
|
|
212
|
-
sessionNamespaces.add(endpoint.sessionNamespace);
|
|
213
|
-
const agent = agentById.get(endpoint.agentId);
|
|
214
|
-
if (!agent) throw new Error(`endpoint ${endpoint.id} references unknown agent: ${endpoint.agentId}`);
|
|
215
|
-
if (!agent.endpointIds.includes(endpoint.id)) throw new Error(`endpoint ${endpoint.id} is not declared by agent ${endpoint.agentId}`);
|
|
216
|
-
}
|
|
217
|
-
for (const agent of manifest.agents) for (const endpointId of agent.endpointIds) {
|
|
218
|
-
const endpoint = manifest.endpoints.find(({ id }) => id === endpointId);
|
|
219
|
-
if (!endpoint) throw new Error(`agent ${agent.agentId} references unknown endpoint: ${endpointId}`);
|
|
220
|
-
if (endpoint.agentId !== agent.agentId) throw new Error(`endpoint ${endpointId} is bound to a different agent`);
|
|
221
|
-
}
|
|
222
|
-
const defaultAgent = agentById.get(manifest.defaultAgentId);
|
|
223
|
-
if (!defaultAgent) throw new Error(`default agent does not exist: ${manifest.defaultAgentId}`);
|
|
224
|
-
const defaultEndpoint = manifest.endpoints.find(({ id }) => id === manifest.defaultEndpointId);
|
|
225
|
-
if (!defaultEndpoint) throw new Error(`default endpoint does not exist: ${manifest.defaultEndpointId}`);
|
|
226
|
-
if (defaultEndpoint.agentId !== defaultAgent.agentId) throw new Error("default endpoint is not bound to the default agent");
|
|
227
|
-
if (!defaultEndpoint.enabled) throw new Error("default endpoint must be enabled");
|
|
228
|
-
if (manifest.backgroundSessions) validateBackgroundSessions(manifest.backgroundSessions);
|
|
229
|
-
}
|
|
230
|
-
function validateBackgroundSessions(config) {
|
|
231
|
-
if (config.required && !config.enabled) throw new Error("backgroundSessions.required requires backgroundSessions.enabled");
|
|
232
|
-
if (config.leaseRenewalIntervalMs >= config.leaseMs) throw new Error("backgroundSessions.leaseRenewalIntervalMs must be shorter than leaseMs");
|
|
233
|
-
if (config.stepTimeoutMs <= 0 || config.maxConcurrentSessions <= 0 || config.leaseMs <= 0) throw new Error("backgroundSessions durations and concurrency must be positive");
|
|
234
|
-
if (config.retryBackoffMs <= 0 || config.sessionLifetimeMs <= 0 || config.maxConsecutiveFailures <= 0) throw new Error("backgroundSessions retry and lifetime limits must be positive");
|
|
235
|
-
}
|
|
236
|
-
function validateRelativeProjectPath(value, owner) {
|
|
237
|
-
if (value.trim() === "" || isAbsolute(value) || value.includes("\0")) throw new Error(`${owner} must be a non-empty relative path`);
|
|
238
|
-
}
|
|
239
|
-
function validateModuleSpecifier(moduleSpecifier) {
|
|
240
|
-
if (moduleSpecifier.trim() === "" || isAbsolute(moduleSpecifier) || /^[a-z][a-z+.-]*:/i.test(moduleSpecifier) || moduleSpecifier.includes("\0")) throw new Error(`invalid plugin module specifier: ${moduleSpecifier}`);
|
|
241
|
-
if ((moduleSpecifier.startsWith("./") || moduleSpecifier.startsWith("../")) && moduleSpecifier.split(/[\\/]/).includes("..")) throw new Error(`plugin module escapes deployment root: ${moduleSpecifier}`);
|
|
242
|
-
}
|
|
243
|
-
async function resolvePluginExport(module) {
|
|
244
|
-
const candidate = "default" in module ? module.default : module;
|
|
245
|
-
const plugin = typeof candidate === "function" ? await candidate() : candidate;
|
|
246
|
-
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");
|
|
247
|
-
return plugin;
|
|
248
|
-
}
|
|
249
|
-
//#endregion
|
|
250
|
-
//#region src/infrastructure/config/local-env-file.ts
|
|
251
|
-
var LocalEnvFileError = class extends Error {
|
|
252
|
-
constructor(message) {
|
|
253
|
-
super(message);
|
|
254
|
-
this.name = "LocalEnvFileError";
|
|
255
|
-
}
|
|
256
|
-
};
|
|
257
|
-
async function loadMergedLocalEnvFile(filePath, overrideEnv) {
|
|
258
|
-
return mergeRivusDaemonEnv(await loadLocalEnvFile(filePath), overrideEnv);
|
|
259
|
-
}
|
|
260
|
-
async function loadLocalEnvFile(filePath) {
|
|
261
|
-
return parseLocalEnvFile(await readFile(filePath, "utf8"));
|
|
262
|
-
}
|
|
263
|
-
function parseLocalEnvFile(contents) {
|
|
264
|
-
const env = {};
|
|
265
|
-
const lines = contents.split(/\r?\n/);
|
|
266
|
-
for (let index = 0; index < lines.length; index += 1) {
|
|
267
|
-
const trimmed = lines[index].trim();
|
|
268
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
269
|
-
const match = (trimmed.startsWith("export ") ? trimmed.slice(7).trimStart() : trimmed).match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
|
|
270
|
-
if (!match) throw new LocalEnvFileError(`Invalid env file line ${index + 1}`);
|
|
271
|
-
const key = match[1];
|
|
272
|
-
const rawValue = match[2];
|
|
273
|
-
env[key] = parseEnvValue(rawValue, index + 1);
|
|
274
|
-
}
|
|
275
|
-
return env;
|
|
276
|
-
}
|
|
277
|
-
function mergeRivusDaemonEnv(fileEnv, overrideEnv) {
|
|
278
|
-
const merged = { ...fileEnv };
|
|
279
|
-
for (const [key, value] of Object.entries(overrideEnv)) if (value !== void 0) merged[key] = value;
|
|
280
|
-
return merged;
|
|
281
|
-
}
|
|
282
|
-
function parseEnvValue(rawValue, lineNumber) {
|
|
283
|
-
const value = rawValue.trim();
|
|
284
|
-
if (!value) return "";
|
|
285
|
-
if (value.startsWith("'")) {
|
|
286
|
-
if (!value.endsWith("'")) throw new LocalEnvFileError(`Invalid single-quoted env value on line ${lineNumber}`);
|
|
287
|
-
return value.slice(1, -1).replaceAll("'\\''", "'");
|
|
288
|
-
}
|
|
289
|
-
if (value.startsWith("\"")) {
|
|
290
|
-
if (!value.endsWith("\"")) throw new LocalEnvFileError(`Invalid double-quoted env value on line ${lineNumber}`);
|
|
291
|
-
return unescapeDoubleQuotedValue(value.slice(1, -1));
|
|
292
|
-
}
|
|
293
|
-
return value;
|
|
294
|
-
}
|
|
295
|
-
function unescapeDoubleQuotedValue(value) {
|
|
296
|
-
return value.replace(/\\(["\\nrt])/g, (_match, escaped) => {
|
|
297
|
-
switch (escaped) {
|
|
298
|
-
case "n": return "\n";
|
|
299
|
-
case "r": return "\r";
|
|
300
|
-
case "t": return " ";
|
|
301
|
-
default: return escaped;
|
|
302
|
-
}
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
//#endregion
|
|
306
|
-
//#region src/infrastructure/config/feishu-endpoint-credentials.ts
|
|
307
|
-
var FeishuEndpointCredentialError = class extends Error {
|
|
308
|
-
name = "FeishuEndpointCredentialError";
|
|
309
|
-
};
|
|
310
|
-
function resolveFeishuEndpointCredentials(credentialRef, env) {
|
|
311
|
-
if (!credentialRef.startsWith("env:")) throw new FeishuEndpointCredentialError("Feishu endpoint credentialRef must use env:<PREFIX>");
|
|
312
|
-
const prefix = credentialRef.slice(4);
|
|
313
|
-
if (!/^[A-Z][A-Z0-9_]*$/.test(prefix)) throw new FeishuEndpointCredentialError(`Invalid environment prefix in credentialRef: ${credentialRef}`);
|
|
314
|
-
return Object.freeze({
|
|
315
|
-
appId: required$1(env, `${prefix}_APP_ID`),
|
|
316
|
-
appSecret: required$1(env, `${prefix}_APP_SECRET`)
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
function required$1(env, variable) {
|
|
320
|
-
const value = env[variable]?.trim();
|
|
321
|
-
if (!value) throw new FeishuEndpointCredentialError(`${variable} is required`);
|
|
322
|
-
return value;
|
|
323
|
-
}
|
|
12
|
+
//#region src/modules/run-presentation/domain/presentation/conversation-progress.ts
|
|
13
|
+
const DEFAULT_CONVERSATION_PROGRESS_DISPLAY = "collapsed";
|
|
324
14
|
//#endregion
|
|
325
|
-
//#region src/domain/card-presentation.ts
|
|
15
|
+
//#region src/modules/card-presentation/domain/presentation/card-presentation.ts
|
|
326
16
|
var CardPresentationTransitionDenied = class extends Error {
|
|
327
17
|
name = "CardPresentationTransitionDenied";
|
|
328
18
|
};
|
|
@@ -424,6 +114,28 @@ function markCardPresentationTerminal(chain, terminalReceiptId) {
|
|
|
424
114
|
terminalReceiptId
|
|
425
115
|
});
|
|
426
116
|
}
|
|
117
|
+
function validateCardPresentationChain(chain) {
|
|
118
|
+
if (chain.runId.trim().length === 0 || !Number.isSafeInteger(chain.activeGeneration) || chain.activeGeneration < 0 || chain.activePresentationId.trim().length === 0 || !Number.isSafeInteger(chain.revision) || chain.revision < 1 || chain.presentations.length === 0) throw new CardPresentationTransitionDenied("card presentation chain is invalid");
|
|
119
|
+
const presentationIds = /* @__PURE__ */ new Set();
|
|
120
|
+
let previous;
|
|
121
|
+
for (const [index, presentation] of chain.presentations.entries()) {
|
|
122
|
+
if (presentation.runId !== chain.runId || presentation.presentationId.trim().length === 0 || presentation.cardId.trim().length === 0 || typeof presentation.sourceMessageId !== "string" || !Number.isSafeInteger(presentation.generation) || presentation.generation !== index || !isCardPresentationStatus(presentation.status) || presentationIds.has(presentation.presentationId)) throw new CardPresentationTransitionDenied(`presentation chain ${chain.runId} contains an invalid entry`);
|
|
123
|
+
assertTimestamp(presentation.createdAt, "presentation creation time");
|
|
124
|
+
assertTimestamp(presentation.leaseDeadlineAt, "presentation lease deadline");
|
|
125
|
+
if (presentation.handoffAt !== void 0) assertTimestamp(presentation.handoffAt, "handoff time");
|
|
126
|
+
if (presentation.handoffFailedAt !== void 0) assertTimestamp(presentation.handoffFailedAt, "handoff failure time");
|
|
127
|
+
if (presentation.status === "terminal" && !presentation.terminalReceiptId?.trim()) throw new CardPresentationTransitionDenied(`terminal presentation ${presentation.presentationId} has no terminal receipt`);
|
|
128
|
+
if (presentation.status !== "terminal" && presentation.terminalReceiptId !== void 0) throw new CardPresentationTransitionDenied(`non-terminal presentation ${presentation.presentationId} carries a terminal receipt`);
|
|
129
|
+
if (previous === void 0) {
|
|
130
|
+
if (presentation.predecessorPresentationId !== void 0) throw new CardPresentationTransitionDenied(`first presentation ${presentation.presentationId} has a predecessor`);
|
|
131
|
+
} else if (presentation.predecessorPresentationId !== previous.presentationId || previous.successorPresentationId !== presentation.presentationId || previous.status !== "closed") throw new CardPresentationTransitionDenied(`presentation chain ${chain.runId} has inconsistent predecessor and successor links`);
|
|
132
|
+
previous = presentation;
|
|
133
|
+
presentationIds.add(presentation.presentationId);
|
|
134
|
+
}
|
|
135
|
+
const active = activeCardPresentation(chain);
|
|
136
|
+
if (chain.activeGeneration !== chain.presentations.length - 1 || active.generation !== chain.activeGeneration || active !== chain.presentations.at(-1) || active.successorPresentationId !== void 0) throw new CardPresentationTransitionDenied(`presentation chain ${chain.runId} has an invalid active generation`);
|
|
137
|
+
return chain;
|
|
138
|
+
}
|
|
427
139
|
function replaceActivePresentation(chain, presentation) {
|
|
428
140
|
return {
|
|
429
141
|
...chain,
|
|
@@ -434,358 +146,748 @@ function replaceActivePresentation(chain, presentation) {
|
|
|
434
146
|
function assertTimestamp(value, label) {
|
|
435
147
|
if (Number.isNaN(Date.parse(value))) throw new CardPresentationTransitionDenied(`${label} must be an ISO timestamp`);
|
|
436
148
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
const RUN_PRESENTATION_SCHEMA_VERSION = 2;
|
|
440
|
-
function hasInspectableRunProgress(presentation) {
|
|
441
|
-
return presentation.steps.some((step) => step.kind === "skill" || step.kind === "tool");
|
|
149
|
+
function isCardPresentationStatus(value) {
|
|
150
|
+
return value === "streaming" || value === "handoff-pending" || value === "active" || value === "closed" || value === "terminal";
|
|
442
151
|
}
|
|
443
152
|
//#endregion
|
|
444
|
-
//#region src/application/
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
};
|
|
463
|
-
const recordNow = (event) => options.clock.now.pipe(Effect.map((at) => record(event, at)));
|
|
464
|
-
const leaseDeadline = (at) => new Date(at.getTime() + leaseMs).toISOString();
|
|
465
|
-
const presentationId = (runId, generation) => `${runId}#${generation}`;
|
|
466
|
-
const publishProgress = (action) => Effect.suspend(() => {
|
|
467
|
-
const chain = options.store.chain(action.runId);
|
|
468
|
-
if (chain && acceptsCardPresentationProgress(chain)) {
|
|
469
|
-
if (action.type === "update_text") latestTextByRun.set(action.runId, action.text);
|
|
470
|
-
else {
|
|
471
|
-
latestPresentationByRun.set(action.runId, action.presentation);
|
|
472
|
-
latestTextByRun.set(action.runId, action.presentation.answer.text);
|
|
473
|
-
}
|
|
474
|
-
const run = liveRuns.get(action.runId);
|
|
475
|
-
if (!run) return options.publisher.publish(action);
|
|
476
|
-
const baseline = generationBaselineByRun.get(action.runId);
|
|
477
|
-
const text = projectText(latestTextByRun.get(action.runId), baseline?.text);
|
|
478
|
-
const presentation = projectPresentation(latestPresentationByRun.get(action.runId), baseline);
|
|
479
|
-
return options.publisher.publish({
|
|
480
|
-
...presentation === void 0 ? {} : { presentation },
|
|
481
|
-
runId: action.runId,
|
|
482
|
-
sessionKey: run.sessionKey,
|
|
483
|
-
...text === void 0 ? {} : { text },
|
|
484
|
-
type: "update_presentation"
|
|
485
|
-
});
|
|
486
|
-
}
|
|
487
|
-
return recordNow({
|
|
488
|
-
...chain ? {
|
|
489
|
-
generation: chain.activeGeneration,
|
|
490
|
-
presentationId: chain.activePresentationId
|
|
491
|
-
} : {},
|
|
492
|
-
runId: action.runId,
|
|
493
|
-
type: "stale_update_dropped"
|
|
494
|
-
});
|
|
495
|
-
});
|
|
496
|
-
const publishTerminal = (action) => {
|
|
497
|
-
const latestText = latestTextByRun.get(action.runId);
|
|
498
|
-
const latestPresentation = latestPresentationByRun.get(action.runId);
|
|
499
|
-
const withText = action.type === "cancel" && action.text === void 0 && latestText !== void 0 ? {
|
|
500
|
-
...action,
|
|
501
|
-
text: latestText
|
|
502
|
-
} : action;
|
|
503
|
-
const projectedAction = projectTerminalAction((withText.type === "cancel" || withText.type === "fail" || withText.type === "finish") && withText.presentation === void 0 && latestPresentation !== void 0 && hasInspectableRunProgress(latestPresentation) ? {
|
|
504
|
-
...withText,
|
|
505
|
-
presentation: latestPresentation
|
|
506
|
-
} : withText, generationBaselineByRun.get(action.runId));
|
|
507
|
-
return options.publisher.publish(projectedAction).pipe(Effect.tapError((error) => recordNow({
|
|
508
|
-
error,
|
|
509
|
-
runId: action.runId,
|
|
510
|
-
type: "terminal_delivery_failed"
|
|
511
|
-
})), Effect.tap(() => options.store.markTerminal({
|
|
512
|
-
runId: action.runId,
|
|
513
|
-
terminalReceiptId: `${action.runId}:${action.type}`
|
|
514
|
-
}).pipe(Effect.catchAll((error) => recordNow({
|
|
515
|
-
error,
|
|
516
|
-
runId: action.runId,
|
|
517
|
-
type: "presentation_write_failed"
|
|
518
|
-
})))), Effect.ensuring(Effect.sync(() => {
|
|
519
|
-
latestPresentationByRun.delete(action.runId);
|
|
520
|
-
latestTextByRun.delete(action.runId);
|
|
521
|
-
generationBaselineByRun.delete(action.runId);
|
|
522
|
-
liveRuns.delete(action.runId);
|
|
523
|
-
})));
|
|
524
|
-
};
|
|
525
|
-
const publishAction = (action) => {
|
|
526
|
-
switch (action.type) {
|
|
527
|
-
case "update_text":
|
|
528
|
-
case "update_progress": return publishProgress(action);
|
|
529
|
-
case "update_presentation": return options.publisher.publish(action);
|
|
530
|
-
default: return publishTerminal(action);
|
|
531
|
-
}
|
|
532
|
-
};
|
|
533
|
-
const runHandoff = (runId) => Effect.gen(function* () {
|
|
534
|
-
const run = liveRuns.get(runId);
|
|
535
|
-
if (!run) return {
|
|
536
|
-
reason: "not-live",
|
|
537
|
-
status: "skipped"
|
|
538
|
-
};
|
|
539
|
-
const startedAt = yield* options.clock.now;
|
|
540
|
-
const chain = options.store.chain(runId);
|
|
541
|
-
if (!chain || !isCardPresentationHandoffDue(chain, startedAt.toISOString())) return {
|
|
542
|
-
reason: "not-due",
|
|
543
|
-
status: "skipped"
|
|
544
|
-
};
|
|
545
|
-
const start = yield* options.store.beginHandoff({
|
|
546
|
-
handoffAt: startedAt.toISOString(),
|
|
547
|
-
runId
|
|
548
|
-
}).pipe(Effect.catchAll(() => Effect.succeed(void 0)));
|
|
549
|
-
if (!start) return {
|
|
550
|
-
reason: "not-streaming",
|
|
551
|
-
status: "skipped"
|
|
552
|
-
};
|
|
553
|
-
if (!start.started) return {
|
|
554
|
-
reason: "already-pending",
|
|
555
|
-
status: "skipped"
|
|
556
|
-
};
|
|
557
|
-
const predecessor = start.presentation;
|
|
558
|
-
const generation = predecessor.generation + 1;
|
|
559
|
-
record({
|
|
560
|
-
cardId: predecessor.cardId,
|
|
561
|
-
generation,
|
|
562
|
-
presentationId: predecessor.presentationId,
|
|
563
|
-
runId,
|
|
564
|
-
sourceMessageId: predecessor.sourceMessageId,
|
|
565
|
-
type: "handoff_started"
|
|
566
|
-
}, startedAt);
|
|
567
|
-
const successorId = presentationId(runId, generation);
|
|
568
|
-
const created = yield* options.createSuccessor({
|
|
569
|
-
generation,
|
|
570
|
-
presentationId: successorId,
|
|
571
|
-
run
|
|
572
|
-
}).pipe(Effect.map((target) => ({ target })), Effect.catchAll((error) => options.store.failHandoff({
|
|
573
|
-
failedAt: startedAt.toISOString(),
|
|
574
|
-
runId
|
|
575
|
-
}).pipe(Effect.catchAll((storeError) => recordNow({
|
|
576
|
-
error: storeError,
|
|
577
|
-
runId,
|
|
578
|
-
type: "presentation_write_failed"
|
|
579
|
-
})), Effect.flatMap(() => recordNow({
|
|
580
|
-
cardId: predecessor.cardId,
|
|
581
|
-
error,
|
|
582
|
-
generation,
|
|
583
|
-
presentationId: predecessor.presentationId,
|
|
584
|
-
runId,
|
|
585
|
-
type: "handoff_failed"
|
|
586
|
-
})), Effect.as({ error }))));
|
|
587
|
-
if ("error" in created) return {
|
|
588
|
-
error: created.error,
|
|
589
|
-
status: "failed"
|
|
590
|
-
};
|
|
591
|
-
const adoptedAt = yield* options.clock.now;
|
|
592
|
-
const presentation = yield* options.store.completeHandoff({
|
|
593
|
-
runId,
|
|
594
|
-
successor: {
|
|
595
|
-
cardId: created.target.cardId,
|
|
596
|
-
createdAt: adoptedAt.toISOString(),
|
|
597
|
-
...created.target.elementId === void 0 ? {} : { elementId: created.target.elementId },
|
|
598
|
-
leaseDeadlineAt: leaseDeadline(adoptedAt),
|
|
599
|
-
presentationId: successorId
|
|
600
|
-
}
|
|
601
|
-
});
|
|
602
|
-
record({
|
|
603
|
-
cardId: predecessor.cardId,
|
|
604
|
-
generation,
|
|
605
|
-
presentationId: successorId,
|
|
606
|
-
runId,
|
|
607
|
-
sourceMessageId: predecessor.sourceMessageId,
|
|
608
|
-
successorCardId: created.target.cardId,
|
|
609
|
-
type: "handoff_succeeded"
|
|
610
|
-
}, adoptedAt);
|
|
611
|
-
generationBaselineByRun.set(runId, {
|
|
612
|
-
...latestPresentationByRun.has(runId) ? { presentation: latestPresentationByRun.get(runId) } : {},
|
|
613
|
-
...latestTextByRun.has(runId) ? { text: latestTextByRun.get(runId) } : {}
|
|
614
|
-
});
|
|
615
|
-
return {
|
|
616
|
-
presentation,
|
|
617
|
-
status: "rolled-over"
|
|
153
|
+
//#region src/modules/card-presentation/application/presentation/card-presentation-service.ts
|
|
154
|
+
var CardPresentationNotFound = class extends Error {
|
|
155
|
+
runId;
|
|
156
|
+
_tag = "CardPresentationNotFound";
|
|
157
|
+
name = "CardPresentationNotFound";
|
|
158
|
+
constructor(runId) {
|
|
159
|
+
super(`card presentation for run ${runId} was not found`);
|
|
160
|
+
this.runId = runId;
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
function createCardPresentationService(repository) {
|
|
164
|
+
const transition = (runId, apply) => repository.transact((current) => {
|
|
165
|
+
const chain = findChain(current, runId);
|
|
166
|
+
if (!chain) throw new CardPresentationNotFound(runId);
|
|
167
|
+
const next = apply(chain);
|
|
168
|
+
return next === chain ? { result: activeCardPresentation(chain) } : {
|
|
169
|
+
next: replaceChain(current, next),
|
|
170
|
+
result: activeCardPresentation(next)
|
|
618
171
|
};
|
|
619
172
|
});
|
|
620
173
|
return {
|
|
621
|
-
|
|
622
|
-
const
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
type: "stream_closed"
|
|
174
|
+
bind: (binding) => repository.transact((current) => {
|
|
175
|
+
const chain = findChain(current, binding.runId);
|
|
176
|
+
if (chain && activeCardPresentation(chain).cardId === binding.cardId) return { result: activeCardPresentation(chain) };
|
|
177
|
+
const created = createCardPresentationChain(binding);
|
|
178
|
+
return {
|
|
179
|
+
next: replaceChain(current, created),
|
|
180
|
+
result: activeCardPresentation(created)
|
|
181
|
+
};
|
|
182
|
+
}),
|
|
183
|
+
beginHandoff: ({ handoffAt, runId }) => repository.transact((current) => {
|
|
184
|
+
const chain = findChain(current, runId);
|
|
185
|
+
if (!chain) throw new CardPresentationNotFound(runId);
|
|
186
|
+
const next = beginCardPresentationHandoff(chain, handoffAt);
|
|
187
|
+
const result = {
|
|
188
|
+
presentation: activeCardPresentation(next),
|
|
189
|
+
started: next !== chain
|
|
190
|
+
};
|
|
191
|
+
return next === chain ? { result } : {
|
|
192
|
+
next: replaceChain(current, next),
|
|
193
|
+
result
|
|
194
|
+
};
|
|
195
|
+
}),
|
|
196
|
+
chain: (runId) => findChain(repository.snapshot(), runId),
|
|
197
|
+
chains: () => repository.snapshot(),
|
|
198
|
+
compensateInterruptedHandoffs: (compensatedAt) => repository.transact((current) => {
|
|
199
|
+
const compensated = [];
|
|
200
|
+
const next = current.map((chain) => {
|
|
201
|
+
const candidate = compensateCardPresentationHandoff(chain, compensatedAt);
|
|
202
|
+
if (candidate !== chain) compensated.push(candidate);
|
|
203
|
+
return candidate;
|
|
652
204
|
});
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
latestPresentationByRun.delete(runId);
|
|
658
|
-
generationBaselineByRun.delete(runId);
|
|
659
|
-
liveRuns.delete(runId);
|
|
205
|
+
return compensated.length === 0 ? { result: [] } : {
|
|
206
|
+
next,
|
|
207
|
+
result: compensated
|
|
208
|
+
};
|
|
660
209
|
}),
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
status: () => ({
|
|
673
|
-
counters: { ...counters },
|
|
674
|
-
leaseMs,
|
|
675
|
-
live: [...liveRuns.keys()].flatMap((runId) => {
|
|
676
|
-
const chain = options.store.chain(runId);
|
|
677
|
-
if (!chain) return [];
|
|
678
|
-
return chain.presentations.filter((presentation) => presentation.presentationId === chain.activePresentationId);
|
|
679
|
-
})
|
|
680
|
-
})
|
|
210
|
+
completeHandoff: ({ runId, successor }) => transition(runId, (chain) => completeCardPresentationHandoff(chain, successor)),
|
|
211
|
+
failHandoff: ({ failedAt, runId }) => transition(runId, (chain) => failCardPresentationHandoff(chain, failedAt)).pipe(Effect.asVoid),
|
|
212
|
+
markTerminal: ({ runId, terminalReceiptId }) => transition(runId, (chain) => markCardPresentationTerminal(chain, terminalReceiptId)).pipe(Effect.asVoid),
|
|
213
|
+
release: (runId) => repository.transact((current) => {
|
|
214
|
+
const next = current.filter((chain) => chain.runId !== runId);
|
|
215
|
+
return next.length === current.length ? { result: void 0 } : {
|
|
216
|
+
next,
|
|
217
|
+
result: void 0
|
|
218
|
+
};
|
|
219
|
+
}),
|
|
220
|
+
size: () => repository.snapshot().length
|
|
681
221
|
};
|
|
682
222
|
}
|
|
683
|
-
function
|
|
684
|
-
|
|
685
|
-
if (text === baseline) return "";
|
|
686
|
-
return text.startsWith(baseline) ? text.slice(baseline.length) : text;
|
|
223
|
+
function findChain(chains, runId) {
|
|
224
|
+
return chains.find((chain) => chain.runId === runId);
|
|
687
225
|
}
|
|
688
|
-
function
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
const
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
},
|
|
707
|
-
omittedStepCount: 0,
|
|
708
|
-
steps,
|
|
709
|
-
totalStepCount: steps.length,
|
|
710
|
-
totalToolCallCount: steps.filter((step) => step.kind === "tool").length
|
|
711
|
-
};
|
|
712
|
-
}
|
|
713
|
-
function projectTerminalAction(action, baseline) {
|
|
714
|
-
if (!baseline || action.type !== "finish" && action.type !== "fail" && action.type !== "cancel") return action;
|
|
715
|
-
const presentation = action.presentation ? projectPresentation(action.presentation, baseline) : void 0;
|
|
716
|
-
if (action.type === "finish") return {
|
|
717
|
-
...presentation === void 0 ? {} : { presentation },
|
|
718
|
-
runId: action.runId,
|
|
719
|
-
text: projectText(action.text, baseline.text) ?? action.text,
|
|
720
|
-
type: "finish"
|
|
721
|
-
};
|
|
722
|
-
if (action.type === "cancel") return {
|
|
723
|
-
...presentation === void 0 ? {} : { presentation },
|
|
724
|
-
...action.reason === void 0 ? {} : { reason: action.reason },
|
|
725
|
-
runId: action.runId,
|
|
726
|
-
...action.text === void 0 ? {} : { text: projectText(action.text, baseline.text) ?? action.text },
|
|
727
|
-
type: "cancel"
|
|
226
|
+
function replaceChain(chains, replacement) {
|
|
227
|
+
const index = chains.findIndex((chain) => chain.runId === replacement.runId);
|
|
228
|
+
if (index === -1) return [...chains, replacement];
|
|
229
|
+
return chains.map((chain, current) => current === index ? replacement : chain);
|
|
230
|
+
}
|
|
231
|
+
//#endregion
|
|
232
|
+
//#region src/modules/card-presentation/domain/delivery/card-delivery.ts
|
|
233
|
+
var CardDeliveryTransitionDenied = class extends Error {
|
|
234
|
+
name = "CardDeliveryTransitionDenied";
|
|
235
|
+
};
|
|
236
|
+
function reserveCardDeliverySequence(current, runId) {
|
|
237
|
+
const record = current ?? initialCardDeliveryRecord(runId);
|
|
238
|
+
assertCardDeliveryIdentity(record, runId);
|
|
239
|
+
if (record.terminalPublished) throw new CardDeliveryTransitionDenied(`card delivery for run ${runId} already has a terminal receipt`);
|
|
240
|
+
const next = {
|
|
241
|
+
...record,
|
|
242
|
+
revision: record.revision + 1,
|
|
243
|
+
sequence: record.sequence + 1
|
|
728
244
|
};
|
|
729
245
|
return {
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
runId: action.runId,
|
|
733
|
-
type: "fail"
|
|
246
|
+
record: next,
|
|
247
|
+
sequence: next.sequence
|
|
734
248
|
};
|
|
735
249
|
}
|
|
736
|
-
function
|
|
250
|
+
function markCardDeliveryTerminal(current, runId) {
|
|
251
|
+
const record = current ?? initialCardDeliveryRecord(runId);
|
|
252
|
+
assertCardDeliveryIdentity(record, runId);
|
|
253
|
+
return record.terminalPublished ? record : {
|
|
254
|
+
...record,
|
|
255
|
+
revision: record.revision + 1,
|
|
256
|
+
terminalPublished: true
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
function validateCardDeliveryRecord(record) {
|
|
260
|
+
if (record.runId.trim().length === 0 || !Number.isSafeInteger(record.revision) || record.revision < 1 || !Number.isSafeInteger(record.sequence) || record.sequence < 0 || typeof record.terminalPublished !== "boolean") throw new CardDeliveryTransitionDenied("card delivery record is invalid");
|
|
261
|
+
return { ...record };
|
|
262
|
+
}
|
|
263
|
+
function validateCardDeliveryTransition(previous, next) {
|
|
264
|
+
validateCardDeliveryRecord(next);
|
|
265
|
+
if (previous === void 0) {
|
|
266
|
+
if (!(next.sequence === 1 && !next.terminalPublished || next.sequence === 0 && next.terminalPublished) || next.revision !== 1) throw new CardDeliveryTransitionDenied(`card delivery for run ${next.runId} has an invalid initial revision`);
|
|
267
|
+
return { ...next };
|
|
268
|
+
}
|
|
269
|
+
assertCardDeliveryIdentity(previous, next.runId);
|
|
270
|
+
if (next.revision !== previous.revision + 1) throw new CardDeliveryTransitionDenied(`card delivery for run ${next.runId} skipped a revision`);
|
|
271
|
+
if (previous.terminalPublished) throw new CardDeliveryTransitionDenied(`card delivery for run ${next.runId} changed after its terminal receipt`);
|
|
272
|
+
if (!(next.terminalPublished ? next.sequence === previous.sequence : next.sequence === previous.sequence + 1)) throw new CardDeliveryTransitionDenied(`card delivery for run ${next.runId} has an invalid sequence transition`);
|
|
273
|
+
return { ...next };
|
|
274
|
+
}
|
|
275
|
+
function initialCardDeliveryRecord(runId) {
|
|
276
|
+
if (runId.trim().length === 0) throw new CardDeliveryTransitionDenied("card delivery run id must not be empty");
|
|
737
277
|
return {
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
presentation_write_failed: 0,
|
|
743
|
-
stale_update_dropped: 0,
|
|
744
|
-
stream_closed: 0,
|
|
745
|
-
terminal_delivery_failed: 0
|
|
278
|
+
revision: 0,
|
|
279
|
+
runId,
|
|
280
|
+
sequence: 0,
|
|
281
|
+
terminalPublished: false
|
|
746
282
|
};
|
|
747
283
|
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
const DEFAULT_BACKGROUND_SESSION_STEP_TIMEOUT_MS = 300 * 1e3;
|
|
751
|
-
const DEFAULT_BACKGROUND_SESSION_MAX_CONCURRENT_SESSIONS = 4;
|
|
752
|
-
const DEFAULT_BACKGROUND_SESSION_LEASE_MS = 3e4;
|
|
753
|
-
const DEFAULT_BACKGROUND_SESSION_LEASE_RENEWAL_INTERVAL_MS = 1e4;
|
|
754
|
-
const DEFAULT_BACKGROUND_SESSION_MAX_CONSECUTIVE_FAILURES = 3;
|
|
755
|
-
const DEFAULT_BACKGROUND_SESSION_RETRY_BACKOFF_MS = 3e4;
|
|
756
|
-
const DEFAULT_BACKGROUND_SESSION_LIFETIME_MS = 1440 * 60 * 1e3;
|
|
757
|
-
const DEFAULT_BACKGROUND_SESSION_SUPERVISOR_INTERVAL_MS = 1e3;
|
|
758
|
-
function resolveBackgroundSessionSupervisorIntervalMs(leaseMs) {
|
|
759
|
-
return Math.min(5e3, Math.max(200, Math.floor(leaseMs / 10)));
|
|
284
|
+
function assertCardDeliveryIdentity(record, runId) {
|
|
285
|
+
if (record.runId !== runId) throw new CardDeliveryTransitionDenied(`card delivery identity ${record.runId} does not match requested run ${runId}`);
|
|
760
286
|
}
|
|
761
287
|
//#endregion
|
|
762
|
-
//#region src/domain/
|
|
763
|
-
const
|
|
288
|
+
//#region src/modules/card-presentation/domain/presentation/card-presentation-lease.ts
|
|
289
|
+
const DEFAULT_CARD_STREAM_LEASE_MS$1 = 51e4;
|
|
290
|
+
function validateCardPresentationLeaseMs(leaseMs) {
|
|
291
|
+
if (!Number.isSafeInteger(leaseMs) || leaseMs < 1) throw new CardPresentationTransitionDenied("card presentation lease must be a positive integer");
|
|
292
|
+
return leaseMs;
|
|
293
|
+
}
|
|
294
|
+
function cardPresentationLeaseDeadline(at, leaseMs) {
|
|
295
|
+
const atMs = Date.parse(at);
|
|
296
|
+
if (Number.isNaN(atMs)) throw new CardPresentationTransitionDenied("card presentation lease start must be an ISO timestamp");
|
|
297
|
+
return new Date(atMs + validateCardPresentationLeaseMs(leaseMs)).toISOString();
|
|
298
|
+
}
|
|
764
299
|
//#endregion
|
|
765
|
-
//#region src/
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
300
|
+
//#region src/platform/runtime/runtime-cache.ts
|
|
301
|
+
function createRuntimeCache() {
|
|
302
|
+
const entries = /* @__PURE__ */ new Map();
|
|
303
|
+
const serial = Effect.unsafeMakeSemaphore(1);
|
|
304
|
+
const reserve = (key) => serial.withPermits(1)(Effect.gen(function* () {
|
|
305
|
+
const current = entries.get(key);
|
|
306
|
+
if (current) return {
|
|
307
|
+
created: false,
|
|
308
|
+
entry: current
|
|
309
|
+
};
|
|
310
|
+
const entry = {
|
|
311
|
+
deferred: yield* Deferred.make(),
|
|
312
|
+
initializationStarted: false,
|
|
313
|
+
key
|
|
314
|
+
};
|
|
315
|
+
entries.set(key, entry);
|
|
316
|
+
return {
|
|
317
|
+
created: true,
|
|
318
|
+
entry
|
|
319
|
+
};
|
|
320
|
+
}));
|
|
321
|
+
const start = (entry, create) => Effect.uninterruptible(Effect.gen(function* () {
|
|
322
|
+
if (!(yield* serial.withPermits(1)(Effect.sync(() => {
|
|
323
|
+
if (entry.initializationStarted) return false;
|
|
324
|
+
entry.initializationStarted = true;
|
|
325
|
+
return true;
|
|
326
|
+
})))) return;
|
|
327
|
+
const initialization = create().pipe(Effect.tapError(() => serial.withPermits(1)(Effect.sync(() => {
|
|
328
|
+
if (entries.get(entry.key) === entry) entries.delete(entry.key);
|
|
329
|
+
}))), Effect.exit, Effect.flatMap((exit) => Deferred.done(entry.deferred, exit)), Effect.asVoid);
|
|
330
|
+
yield* Effect.forkDaemon(initialization);
|
|
331
|
+
}));
|
|
332
|
+
const getOrCreate = (key, create) => Effect.uninterruptibleMask((restore) => Effect.gen(function* () {
|
|
333
|
+
const selected = yield* reserve(key);
|
|
334
|
+
yield* start(selected.entry, create);
|
|
335
|
+
return {
|
|
336
|
+
entry: selected.entry,
|
|
337
|
+
runtime: yield* restore(Deferred.await(selected.entry.deferred))
|
|
338
|
+
};
|
|
339
|
+
}));
|
|
340
|
+
return {
|
|
341
|
+
drain: () => serial.withPermits(1)(Effect.sync(() => {
|
|
342
|
+
const drained = Object.freeze([...entries.values()]);
|
|
343
|
+
entries.clear();
|
|
344
|
+
return drained;
|
|
345
|
+
})),
|
|
346
|
+
getExisting: (key) => Effect.gen(function* () {
|
|
347
|
+
const entry = yield* serial.withPermits(1)(Effect.sync(() => entries.get(key)));
|
|
348
|
+
if (!entry) return void 0;
|
|
349
|
+
return {
|
|
350
|
+
entry,
|
|
351
|
+
runtime: yield* Deferred.await(entry.deferred)
|
|
352
|
+
};
|
|
353
|
+
}),
|
|
354
|
+
getOrCreate,
|
|
355
|
+
isCurrent: (key, entry) => serial.withPermits(1)(Effect.sync(() => entries.get(key) === entry)),
|
|
356
|
+
reserve,
|
|
357
|
+
size: () => serial.withPermits(1)(Effect.sync(() => entries.size)),
|
|
358
|
+
start
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
function disposeRuntimeCacheEntries(options) {
|
|
362
|
+
const disposal = Effect.gen(function* () {
|
|
363
|
+
const completions = yield* Effect.forEach(options.entries, (entry) => Effect.gen(function* () {
|
|
364
|
+
const completion = yield* Deferred.make();
|
|
365
|
+
yield* Effect.forkDaemon(Deferred.await(entry.deferred).pipe(Effect.flatMap(options.dispose), Effect.exit, Effect.flatMap((exit) => Deferred.succeed(completion, exit)), Effect.asVoid));
|
|
366
|
+
return completion;
|
|
367
|
+
}), { concurrency: "unbounded" });
|
|
368
|
+
const failures = (yield* Effect.forEach(completions, Deferred.await, { concurrency: "unbounded" })).filter(Exit.isFailure).map(({ cause }) => Cause.squash(cause));
|
|
369
|
+
if (failures.length > 0) return yield* Effect.fail(new AggregateError(failures, options.failureMessage));
|
|
370
|
+
});
|
|
371
|
+
return options.timeout ? disposal.pipe(Effect.timeoutFail({
|
|
372
|
+
duration: options.timeout.milliseconds,
|
|
373
|
+
onTimeout: options.timeout.onTimeout
|
|
374
|
+
})) : disposal;
|
|
375
|
+
}
|
|
376
|
+
function invokeRuntimeControl(runtime, control) {
|
|
377
|
+
return runtime.pipe(Effect.flatMap((selected) => selected ? control(selected) ?? Effect.succeed(false) : Effect.succeed(false)));
|
|
378
|
+
}
|
|
379
|
+
//#endregion
|
|
380
|
+
//#region src/adapters/agent/runtime/process-agent-runtime-adapter.ts
|
|
381
|
+
function toEffectAgentRuntimeInput(input) {
|
|
382
|
+
const onUpdate = input.onUpdate;
|
|
383
|
+
return {
|
|
384
|
+
...runtimeInputFields(input),
|
|
385
|
+
...onUpdate ? { onUpdate: (update) => Effect.tryPromise({
|
|
386
|
+
try: async () => onUpdate(update),
|
|
387
|
+
catch: (failure) => failure
|
|
388
|
+
}) } : {}
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
function toProcessAgentRuntimeInput(input, runEffect) {
|
|
392
|
+
const onUpdate = input.onUpdate;
|
|
393
|
+
return {
|
|
394
|
+
...runtimeInputFields(input),
|
|
395
|
+
...onUpdate ? { onUpdate: (update) => runEffect(onUpdate(update)) } : {}
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
function toEffectAgentRuntime(runtime, runEffect) {
|
|
399
|
+
const cancel = runtime.cancel?.bind(runtime);
|
|
400
|
+
const dispose = runtime.dispose?.bind(runtime);
|
|
401
|
+
const steer = runtime.steer?.bind(runtime);
|
|
402
|
+
return {
|
|
403
|
+
...runtime.concurrency ? { concurrency: runtime.concurrency } : {},
|
|
404
|
+
...cancel ? { cancel: (input) => Effect.tryPromise({
|
|
405
|
+
try: () => cancel(input),
|
|
406
|
+
catch: (failure) => failure
|
|
407
|
+
}) } : {},
|
|
408
|
+
...dispose ? { dispose: () => Effect.tryPromise({
|
|
409
|
+
try: async () => dispose(),
|
|
410
|
+
catch: (failure) => failure
|
|
411
|
+
}) } : {},
|
|
412
|
+
run: (input) => Effect.tryPromise({
|
|
413
|
+
try: () => runtime.run(toProcessAgentRuntimeInput(input, runEffect)),
|
|
414
|
+
catch: (failure) => failure
|
|
415
|
+
}),
|
|
416
|
+
...steer ? { steer: (input) => Effect.tryPromise({
|
|
417
|
+
try: () => steer(input),
|
|
418
|
+
catch: (failure) => failure
|
|
419
|
+
}) } : {}
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
function runtimeInputFields(input) {
|
|
423
|
+
return {
|
|
424
|
+
...input.invocation ? { invocation: input.invocation } : {},
|
|
425
|
+
...input.payload === void 0 ? {} : { payload: input.payload },
|
|
426
|
+
sessionKey: input.sessionKey,
|
|
427
|
+
text: input.text
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
//#endregion
|
|
431
|
+
//#region src/platform/home/config/runtime/local-env-file.ts
|
|
432
|
+
var LocalEnvFileError = class extends Error {
|
|
433
|
+
constructor(message) {
|
|
434
|
+
super(message);
|
|
435
|
+
this.name = "LocalEnvFileError";
|
|
772
436
|
}
|
|
773
437
|
};
|
|
774
|
-
async function
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
438
|
+
async function loadMergedLocalEnvFile(filePath, overrideEnv) {
|
|
439
|
+
return mergeRivusDaemonEnv(await loadLocalEnvFile(filePath), overrideEnv);
|
|
440
|
+
}
|
|
441
|
+
async function loadLocalEnvFile(filePath) {
|
|
442
|
+
return parseLocalEnvFile(await readFile(filePath, "utf8"));
|
|
443
|
+
}
|
|
444
|
+
function parseLocalEnvFile(contents) {
|
|
445
|
+
const env = {};
|
|
446
|
+
const lines = contents.split(/\r?\n/);
|
|
447
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
448
|
+
const trimmed = lines[index].trim();
|
|
449
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
450
|
+
const match = (trimmed.startsWith("export ") ? trimmed.slice(7).trimStart() : trimmed).match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
|
|
451
|
+
if (!match) throw new LocalEnvFileError(`Invalid env file line ${index + 1}`);
|
|
452
|
+
const key = match[1];
|
|
453
|
+
const rawValue = match[2];
|
|
454
|
+
env[key] = parseEnvValue(rawValue, index + 1);
|
|
455
|
+
}
|
|
456
|
+
return env;
|
|
457
|
+
}
|
|
458
|
+
function mergeRivusDaemonEnv(fileEnv, overrideEnv) {
|
|
459
|
+
const merged = { ...fileEnv };
|
|
460
|
+
for (const [key, value] of Object.entries(overrideEnv)) if (value !== void 0) merged[key] = value;
|
|
461
|
+
return merged;
|
|
462
|
+
}
|
|
463
|
+
function parseEnvValue(rawValue, lineNumber) {
|
|
464
|
+
const value = rawValue.trim();
|
|
465
|
+
if (!value) return "";
|
|
466
|
+
if (value.startsWith("'")) {
|
|
467
|
+
if (!value.endsWith("'")) throw new LocalEnvFileError(`Invalid single-quoted env value on line ${lineNumber}`);
|
|
468
|
+
return value.slice(1, -1).replaceAll("'\\''", "'");
|
|
469
|
+
}
|
|
470
|
+
if (value.startsWith("\"")) {
|
|
471
|
+
if (!value.endsWith("\"")) throw new LocalEnvFileError(`Invalid double-quoted env value on line ${lineNumber}`);
|
|
472
|
+
return unescapeDoubleQuotedValue(value.slice(1, -1));
|
|
473
|
+
}
|
|
474
|
+
return value;
|
|
475
|
+
}
|
|
476
|
+
function unescapeDoubleQuotedValue(value) {
|
|
477
|
+
return value.replace(/\\(["\\nrt])/g, (_match, escaped) => {
|
|
478
|
+
switch (escaped) {
|
|
479
|
+
case "n": return "\n";
|
|
480
|
+
case "r": return "\r";
|
|
481
|
+
case "t": return " ";
|
|
482
|
+
default: return escaped;
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
//#endregion
|
|
487
|
+
//#region src/platform/home/config/runtime/rivus-daemon-config.ts
|
|
488
|
+
var RivusDaemonConfigError = class {
|
|
489
|
+
variable;
|
|
490
|
+
message;
|
|
491
|
+
_tag = "RivusDaemonConfigError";
|
|
492
|
+
constructor(variable, message) {
|
|
493
|
+
this.variable = variable;
|
|
494
|
+
this.message = message;
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
const DEFAULT_AGENT_ID$1 = "main";
|
|
498
|
+
const DEFAULT_CARD_STREAM_LEASE_MS = 51e4;
|
|
499
|
+
const DEFAULT_FEISHU_BASE_URL$1 = "https://open.feishu.cn";
|
|
500
|
+
const DEFAULT_STREAM_MIN_INTERVAL_MS$1 = 200;
|
|
501
|
+
const THINKING_LEVELS$1 = /* @__PURE__ */ new Set([
|
|
502
|
+
"off",
|
|
503
|
+
"minimal",
|
|
504
|
+
"low",
|
|
505
|
+
"medium",
|
|
506
|
+
"high",
|
|
507
|
+
"xhigh"
|
|
508
|
+
]);
|
|
509
|
+
function loadRivusDaemonConfig(env, options = {}) {
|
|
510
|
+
return Effect.gen(function* () {
|
|
511
|
+
const appId = yield* required$1(env, "FEISHU_APP_ID");
|
|
512
|
+
const appSecret = yield* required$1(env, "FEISHU_APP_SECRET");
|
|
513
|
+
const streamMinIntervalMs = yield* optionalPositiveInteger(env.FEISHU_STREAM_MIN_INTERVAL_MS, "FEISHU_STREAM_MIN_INTERVAL_MS", DEFAULT_STREAM_MIN_INTERVAL_MS$1);
|
|
514
|
+
const cardStreamLeaseMs = yield* optionalPositiveInteger(env.FEISHU_CARD_STREAM_LEASE_MS, "FEISHU_CARD_STREAM_LEASE_MS", DEFAULT_CARD_STREAM_LEASE_MS);
|
|
515
|
+
const thinkingLevel = yield* optionalThinkingLevel(env.PI_THINKING_LEVEL);
|
|
516
|
+
const apiKey = yield* optionalPiApiKey(env, options.readTextFile ?? readUtf8File);
|
|
517
|
+
const baseUrl = optional(env.PI_BASE_URL);
|
|
518
|
+
const model = optional(env.PI_MODEL);
|
|
519
|
+
return {
|
|
520
|
+
agentId: optional(env.RIVUS_AGENT_ID) ?? DEFAULT_AGENT_ID$1,
|
|
521
|
+
feishu: {
|
|
522
|
+
appId,
|
|
523
|
+
appSecret,
|
|
524
|
+
baseUrl: optional(env.FEISHU_BASE_URL) ?? DEFAULT_FEISHU_BASE_URL$1,
|
|
525
|
+
cardStreamLeaseMs,
|
|
526
|
+
streamMinIntervalMs
|
|
527
|
+
},
|
|
528
|
+
pi: {
|
|
529
|
+
...apiKey ? { apiKey } : {},
|
|
530
|
+
...baseUrl ? { baseUrl } : {},
|
|
531
|
+
...model ? { model } : {},
|
|
532
|
+
...thinkingLevel ? { thinkingLevel } : {}
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
function optional(value) {
|
|
538
|
+
const trimmed = value?.trim();
|
|
539
|
+
return trimmed ? trimmed : void 0;
|
|
540
|
+
}
|
|
541
|
+
function readUtf8File(path) {
|
|
542
|
+
return readFile(path, "utf8");
|
|
543
|
+
}
|
|
544
|
+
function optionalPiApiKey(env, readTextFile) {
|
|
545
|
+
const inlineApiKey = optional(env.PI_API_KEY);
|
|
546
|
+
const apiKeyFile = optional(env.PI_API_KEY_FILE);
|
|
547
|
+
if (inlineApiKey && apiKeyFile) return Effect.fail(new RivusDaemonConfigError("PI_API_KEY", "PI_API_KEY and PI_API_KEY_FILE cannot both be set"));
|
|
548
|
+
if (inlineApiKey) return Effect.succeed(inlineApiKey);
|
|
549
|
+
if (!apiKeyFile) return Effect.succeed(void 0);
|
|
550
|
+
return Effect.tryPromise({
|
|
551
|
+
try: async () => readTextFile(apiKeyFile),
|
|
552
|
+
catch: (error) => new RivusDaemonConfigError("PI_API_KEY_FILE", `PI_API_KEY_FILE could not be read: ${formatConfigError(error)}`)
|
|
553
|
+
}).pipe(Effect.flatMap((contents) => {
|
|
554
|
+
const apiKey = optional(contents);
|
|
555
|
+
return apiKey ? Effect.succeed(apiKey) : Effect.fail(new RivusDaemonConfigError("PI_API_KEY_FILE", "PI_API_KEY_FILE must not be empty"));
|
|
556
|
+
}));
|
|
557
|
+
}
|
|
558
|
+
function formatConfigError(error) {
|
|
559
|
+
return error instanceof Error ? error.message : String(error);
|
|
560
|
+
}
|
|
561
|
+
function optionalPositiveInteger(value, variable, fallback) {
|
|
562
|
+
const normalized = optional(value);
|
|
563
|
+
if (!normalized) return Effect.succeed(fallback);
|
|
564
|
+
if (/^[1-9]\d*$/.test(normalized)) return Effect.succeed(Number(normalized));
|
|
565
|
+
return Effect.fail(new RivusDaemonConfigError(variable, `${variable} must be a positive integer`));
|
|
566
|
+
}
|
|
567
|
+
function required$1(env, variable) {
|
|
568
|
+
const value = optional(env[variable]);
|
|
569
|
+
if (value) return Effect.succeed(value);
|
|
570
|
+
return Effect.fail(new RivusDaemonConfigError(variable, `${variable} is required`));
|
|
571
|
+
}
|
|
572
|
+
function optionalThinkingLevel(value) {
|
|
573
|
+
const normalized = optional(value);
|
|
574
|
+
if (!normalized) return Effect.succeed(void 0);
|
|
575
|
+
if (THINKING_LEVELS$1.has(normalized)) return Effect.succeed(normalized);
|
|
576
|
+
return Effect.fail(new RivusDaemonConfigError("PI_THINKING_LEVEL", "PI_THINKING_LEVEL must be one of off, minimal, low, medium, high, xhigh"));
|
|
577
|
+
}
|
|
578
|
+
//#endregion
|
|
579
|
+
//#region src/adapters/feishu/config/feishu-endpoint-credentials.ts
|
|
580
|
+
var FeishuEndpointCredentialError = class extends Error {
|
|
581
|
+
name = "FeishuEndpointCredentialError";
|
|
582
|
+
};
|
|
583
|
+
function resolveFeishuEndpointCredentials(credentialRef, env) {
|
|
584
|
+
if (!credentialRef.startsWith("env:")) throw new FeishuEndpointCredentialError("Feishu endpoint credentialRef must use env:<PREFIX>");
|
|
585
|
+
const prefix = credentialRef.slice(4);
|
|
586
|
+
if (!/^[A-Z][A-Z0-9_]*$/.test(prefix)) throw new FeishuEndpointCredentialError(`Invalid environment prefix in credentialRef: ${credentialRef}`);
|
|
587
|
+
return Object.freeze({
|
|
588
|
+
appId: required(env, `${prefix}_APP_ID`),
|
|
589
|
+
appSecret: required(env, `${prefix}_APP_SECRET`)
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
function required(env, variable) {
|
|
593
|
+
const value = env[variable]?.trim();
|
|
594
|
+
if (!value) throw new FeishuEndpointCredentialError(`${variable} is required`);
|
|
595
|
+
return value;
|
|
596
|
+
}
|
|
597
|
+
//#endregion
|
|
598
|
+
//#region src/adapters/openclaw/config/openclaw-env-import.ts
|
|
599
|
+
var OpenClawEnvImportError = class extends Error {
|
|
600
|
+
constructor(message) {
|
|
601
|
+
super(message);
|
|
602
|
+
this.name = "OpenClawEnvImportError";
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
const DEFAULT_AGENT_ID = "main";
|
|
606
|
+
const DEFAULT_FEISHU_BASE_URL = "https://open.feishu.cn";
|
|
607
|
+
const DEFAULT_LARK_BASE_URL = "https://open.larksuite.com";
|
|
608
|
+
const DEFAULT_STREAM_MIN_INTERVAL_MS = 200;
|
|
609
|
+
const THINKING_LEVELS = /* @__PURE__ */ new Set([
|
|
610
|
+
"off",
|
|
611
|
+
"minimal",
|
|
612
|
+
"low",
|
|
613
|
+
"medium",
|
|
614
|
+
"high",
|
|
615
|
+
"xhigh"
|
|
616
|
+
]);
|
|
617
|
+
const ENV_FILE_ORDER = [
|
|
618
|
+
"FEISHU_APP_ID",
|
|
619
|
+
"FEISHU_APP_SECRET",
|
|
620
|
+
"FEISHU_BASE_URL",
|
|
621
|
+
"FEISHU_STREAM_MIN_INTERVAL_MS",
|
|
622
|
+
"RIVUS_AGENT_ID",
|
|
623
|
+
"PI_API_KEY_FILE",
|
|
624
|
+
"PI_BASE_URL",
|
|
625
|
+
"PI_MODEL",
|
|
626
|
+
"PI_THINKING_LEVEL"
|
|
627
|
+
];
|
|
628
|
+
function createRivusEnvFromOpenClawConfig(openClawConfig, options = {}) {
|
|
629
|
+
const config = asRecord(openClawConfig, "OpenClaw config");
|
|
630
|
+
const feishu = asRecord(readPath(config, ["channels", "feishu"]), "channels.feishu");
|
|
631
|
+
const appId = requiredString(feishu, "appId", "channels.feishu.appId");
|
|
632
|
+
const appSecret = requiredString(feishu, "appSecret", "channels.feishu.appSecret");
|
|
633
|
+
const modelReference = findPrimaryModelReference(config);
|
|
634
|
+
const providerId = modelReference ? parseProviderId(modelReference) : void 0;
|
|
635
|
+
const provider = providerId ? optionalRecord(readPath(config, [
|
|
636
|
+
"models",
|
|
637
|
+
"providers",
|
|
638
|
+
providerId
|
|
639
|
+
])) : void 0;
|
|
640
|
+
const providerBaseUrl = provider ? optionalString(provider.baseUrl) : void 0;
|
|
641
|
+
const thinkingLevel = modelReference ? findThinkingLevel(config, modelReference) : void 0;
|
|
642
|
+
const warnings = thinkingLevel?.warning ? [thinkingLevel.warning] : [];
|
|
643
|
+
return {
|
|
644
|
+
env: {
|
|
645
|
+
FEISHU_APP_ID: appId,
|
|
646
|
+
FEISHU_APP_SECRET: appSecret,
|
|
647
|
+
FEISHU_BASE_URL: options.feishuBaseUrl ?? inferFeishuBaseUrl(optionalString(feishu.domain)),
|
|
648
|
+
FEISHU_STREAM_MIN_INTERVAL_MS: String(options.streamMinIntervalMs ?? DEFAULT_STREAM_MIN_INTERVAL_MS),
|
|
649
|
+
RIVUS_AGENT_ID: findAgentId(config),
|
|
650
|
+
...options.piApiKeyFile ? { PI_API_KEY_FILE: options.piApiKeyFile } : {},
|
|
651
|
+
...providerBaseUrl ? { PI_BASE_URL: providerBaseUrl } : {},
|
|
652
|
+
...modelReference ? { PI_MODEL: modelReference } : {},
|
|
653
|
+
...thinkingLevel?.level ? { PI_THINKING_LEVEL: thinkingLevel.level } : {}
|
|
654
|
+
},
|
|
655
|
+
warnings
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
function formatRivusEnvFile(env) {
|
|
659
|
+
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`;
|
|
660
|
+
}
|
|
661
|
+
function findAgentId(config) {
|
|
662
|
+
const agents = optionalRecord(config.agents);
|
|
663
|
+
return optionalString(optionalRecord((Array.isArray(agents?.list) ? agents.list : [])[0])?.id) ?? DEFAULT_AGENT_ID;
|
|
664
|
+
}
|
|
665
|
+
function findPrimaryModelReference(config) {
|
|
666
|
+
const primary = optionalString(readPath(config, [
|
|
667
|
+
"agents",
|
|
668
|
+
"defaults",
|
|
669
|
+
"model",
|
|
670
|
+
"primary"
|
|
671
|
+
]));
|
|
672
|
+
if (primary) return primary;
|
|
673
|
+
const providers = optionalRecord(readPath(config, ["models", "providers"]));
|
|
674
|
+
if (!providers) return;
|
|
675
|
+
for (const [providerId, provider] of Object.entries(providers)) {
|
|
676
|
+
const model = firstModelId(optionalRecord(provider));
|
|
677
|
+
if (model) return `${providerId}/${model}`;
|
|
786
678
|
}
|
|
787
679
|
}
|
|
788
|
-
function
|
|
680
|
+
function firstModelId(provider) {
|
|
681
|
+
return optionalString(optionalRecord((Array.isArray(provider?.models) ? provider.models : [])[0])?.id);
|
|
682
|
+
}
|
|
683
|
+
function parseProviderId(modelReference) {
|
|
684
|
+
const separator = modelReference.indexOf("/");
|
|
685
|
+
return separator > 0 ? modelReference.slice(0, separator) : void 0;
|
|
686
|
+
}
|
|
687
|
+
function findThinkingLevel(config, modelReference) {
|
|
688
|
+
const providerId = parseProviderId(modelReference);
|
|
689
|
+
const modelId = readModelId(modelReference);
|
|
690
|
+
const rawLevel = [
|
|
691
|
+
readPath(config, [
|
|
692
|
+
"agents",
|
|
693
|
+
"defaults",
|
|
694
|
+
"models",
|
|
695
|
+
modelReference,
|
|
696
|
+
"thinkingLevel"
|
|
697
|
+
]),
|
|
698
|
+
readPath(config, [
|
|
699
|
+
"agents",
|
|
700
|
+
"defaults",
|
|
701
|
+
"models",
|
|
702
|
+
modelReference,
|
|
703
|
+
"thinkLevel"
|
|
704
|
+
]),
|
|
705
|
+
readPath(config, [
|
|
706
|
+
"agents",
|
|
707
|
+
"defaults",
|
|
708
|
+
"models",
|
|
709
|
+
modelReference,
|
|
710
|
+
"reasoningLevel"
|
|
711
|
+
]),
|
|
712
|
+
...providerId && modelId ? readProviderModelThinkingCandidates(config, providerId, modelId) : []
|
|
713
|
+
].map(optionalString).find(Boolean);
|
|
714
|
+
if (!rawLevel) return;
|
|
715
|
+
if (THINKING_LEVELS.has(rawLevel)) return { level: rawLevel };
|
|
716
|
+
return { warning: `Unsupported OpenClaw thinking level '${rawLevel}' was ignored` };
|
|
717
|
+
}
|
|
718
|
+
function readProviderModelThinkingCandidates(config, providerId, modelId) {
|
|
719
|
+
const providerModels = readPath(config, [
|
|
720
|
+
"models",
|
|
721
|
+
"providers",
|
|
722
|
+
providerId,
|
|
723
|
+
"models"
|
|
724
|
+
]);
|
|
725
|
+
if (!Array.isArray(providerModels)) return [];
|
|
726
|
+
const model = providerModels.map(optionalRecord).find((candidate) => optionalString(candidate?.id) === modelId);
|
|
727
|
+
if (!model) return [];
|
|
728
|
+
return [
|
|
729
|
+
model.thinkingLevel,
|
|
730
|
+
model.thinkLevel,
|
|
731
|
+
model.reasoningLevel,
|
|
732
|
+
readPath(model, ["reasoning", "level"]),
|
|
733
|
+
readPath(model, ["reasoning", "thinkingLevel"]),
|
|
734
|
+
readPath(model, ["reasoning", "thinkLevel"])
|
|
735
|
+
];
|
|
736
|
+
}
|
|
737
|
+
function readModelId(modelReference) {
|
|
738
|
+
const separator = modelReference.indexOf("/");
|
|
739
|
+
return separator >= 0 && separator < modelReference.length - 1 ? modelReference.slice(separator + 1) : void 0;
|
|
740
|
+
}
|
|
741
|
+
function inferFeishuBaseUrl(domain) {
|
|
742
|
+
return domain === "lark" ? DEFAULT_LARK_BASE_URL : DEFAULT_FEISHU_BASE_URL;
|
|
743
|
+
}
|
|
744
|
+
function quoteEnvValue(value) {
|
|
745
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
746
|
+
}
|
|
747
|
+
function requiredString(record, key, path) {
|
|
748
|
+
const value = optionalString(record[key]);
|
|
749
|
+
if (!value) throw new OpenClawEnvImportError(`${path} is required`);
|
|
750
|
+
return value;
|
|
751
|
+
}
|
|
752
|
+
function optionalString(value) {
|
|
753
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
754
|
+
}
|
|
755
|
+
function asRecord(value, path) {
|
|
756
|
+
const record = optionalRecord(value);
|
|
757
|
+
if (!record) throw new OpenClawEnvImportError(`${path} must be an object`);
|
|
758
|
+
return record;
|
|
759
|
+
}
|
|
760
|
+
function optionalRecord(value) {
|
|
761
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
762
|
+
}
|
|
763
|
+
function readPath(record, path) {
|
|
764
|
+
let value = record;
|
|
765
|
+
for (const segment of path) {
|
|
766
|
+
const current = optionalRecord(value);
|
|
767
|
+
if (!current) return;
|
|
768
|
+
value = current[segment];
|
|
769
|
+
}
|
|
770
|
+
return value;
|
|
771
|
+
}
|
|
772
|
+
//#endregion
|
|
773
|
+
//#region src/platform/daemon/process/rivus-daemon-shutdown-controller.ts
|
|
774
|
+
const DEFAULT_SIGNALS = ["SIGINT", "SIGTERM"];
|
|
775
|
+
function createRivusDaemonShutdownController(options) {
|
|
776
|
+
let shutdown;
|
|
777
|
+
const handle = (signal) => {
|
|
778
|
+
shutdown ??= Effect.runPromise(options.daemon.stop()).then(() => options.onStopped?.(signal)).catch(async (error) => {
|
|
779
|
+
await options.onError?.(error, signal);
|
|
780
|
+
throw error;
|
|
781
|
+
});
|
|
782
|
+
return shutdown;
|
|
783
|
+
};
|
|
784
|
+
return {
|
|
785
|
+
handle,
|
|
786
|
+
install: () => {
|
|
787
|
+
for (const signal of options.signals ?? DEFAULT_SIGNALS) options.signalSource.on(signal, () => {
|
|
788
|
+
handle(signal);
|
|
789
|
+
});
|
|
790
|
+
},
|
|
791
|
+
stopping: () => shutdown !== void 0
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
//#endregion
|
|
795
|
+
//#region src/modules/project-space/application/workspace/workspace-instructions.ts
|
|
796
|
+
var InvalidWorkspaceRoot = class extends Error {
|
|
797
|
+
name = "InvalidWorkspaceRoot";
|
|
798
|
+
};
|
|
799
|
+
var WorkspaceInstructionsSourceError = class extends Error {
|
|
800
|
+
relativePath;
|
|
801
|
+
reason;
|
|
802
|
+
name = "WorkspaceInstructionsSourceError";
|
|
803
|
+
constructor(relativePath, reason) {
|
|
804
|
+
super(`${relativePath}: ${reason}`);
|
|
805
|
+
this.relativePath = relativePath;
|
|
806
|
+
this.reason = reason;
|
|
807
|
+
}
|
|
808
|
+
};
|
|
809
|
+
function validateWorkspaceInstructionsBudget(maxBytes) {
|
|
810
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) throw new InvalidWorkspaceRoot("workspace instructions maxBytes must be a non-negative integer");
|
|
811
|
+
}
|
|
812
|
+
function assembleWorkspaceInstructions(input) {
|
|
813
|
+
validateWorkspaceInstructionsBudget(input.maxBytes);
|
|
814
|
+
const diagnostics = [...input.diagnostics];
|
|
815
|
+
const selected = [];
|
|
816
|
+
let usedBytes = 0;
|
|
817
|
+
for (const source of [...input.sources].reverse()) if (usedBytes + source.bytes <= input.maxBytes) {
|
|
818
|
+
selected.push(source);
|
|
819
|
+
usedBytes += source.bytes;
|
|
820
|
+
} else diagnostics.push({
|
|
821
|
+
code: "workspace-instructions-source-omitted",
|
|
822
|
+
reason: `source exceeds remaining byte budget of ${input.maxBytes - usedBytes}`,
|
|
823
|
+
relativePath: source.relativePath
|
|
824
|
+
});
|
|
825
|
+
selected.reverse();
|
|
826
|
+
const presented = new Set(input.presentedDigests ?? []);
|
|
827
|
+
const unseen = selected.filter((source) => !presented.has(source.digest));
|
|
828
|
+
const refreshRequired = input.operation === "mutate" && input.targetPath !== void 0 && unseen.length > 0;
|
|
829
|
+
if (refreshRequired) diagnostics.push({
|
|
830
|
+
code: "workspace-instructions-refresh-required",
|
|
831
|
+
reason: `mutation target has ${unseen.length} workspace instruction source(s) not yet presented`
|
|
832
|
+
});
|
|
833
|
+
const view = {
|
|
834
|
+
content: selected.map((source) => source.content).join("\n"),
|
|
835
|
+
diagnostics: Object.freeze(diagnostics),
|
|
836
|
+
digest: createSha256Digest(JSON.stringify(selected.map(({ digest }) => digest))),
|
|
837
|
+
refreshRequired,
|
|
838
|
+
sources: Object.freeze(selected),
|
|
839
|
+
truncated: selected.length !== input.sources.length || input.diagnostics.length > 0
|
|
840
|
+
};
|
|
841
|
+
return Object.freeze(view);
|
|
842
|
+
}
|
|
843
|
+
//#endregion
|
|
844
|
+
//#region src/platform/identity/stable-id.ts
|
|
845
|
+
function createStableId(prefix, value) {
|
|
846
|
+
return `${prefix}:${createHash("sha256").update(JSON.stringify(value)).digest("hex")}`;
|
|
847
|
+
}
|
|
848
|
+
//#endregion
|
|
849
|
+
//#region src/modules/project-space/application/space/project-space-declaration.ts
|
|
850
|
+
function createRivusProjectSpaceDeclarationRevision(declaration) {
|
|
851
|
+
return createStableId("project-space-declaration", {
|
|
852
|
+
id: declaration.id,
|
|
853
|
+
root: declaration.root,
|
|
854
|
+
skills: { sources: declaration.skills.sources },
|
|
855
|
+
workingDirectory: declaration.workingDirectory
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
//#endregion
|
|
859
|
+
//#region src/modules/project-space/domain/space/project-space.ts
|
|
860
|
+
var InvalidRivusProjectSpace = class extends Error {
|
|
861
|
+
name = "InvalidRivusProjectSpace";
|
|
862
|
+
};
|
|
863
|
+
function validateRivusProjectSpaceDeployments(declarations) {
|
|
864
|
+
const projectSpaceIds = /* @__PURE__ */ new Set();
|
|
865
|
+
for (const declaration of declarations) {
|
|
866
|
+
if (projectSpaceIds.has(declaration.id)) throw new InvalidRivusProjectSpace(`duplicate project space: ${declaration.id}`);
|
|
867
|
+
projectSpaceIds.add(declaration.id);
|
|
868
|
+
validateRivusProjectSpaceDeployment(declaration);
|
|
869
|
+
}
|
|
870
|
+
return projectSpaceIds;
|
|
871
|
+
}
|
|
872
|
+
function validateRivusProjectSpaceDeployment(declaration) {
|
|
873
|
+
validateRelativeProjectPath(declaration.root, `project space ${declaration.id} root`);
|
|
874
|
+
validateRelativeProjectPath(declaration.workingDirectory, `project space ${declaration.id} working directory`);
|
|
875
|
+
const sources = /* @__PURE__ */ new Set();
|
|
876
|
+
for (const source of declaration.skills.sources) {
|
|
877
|
+
validateRelativeProjectPath(source, `project space ${declaration.id} Skill source`);
|
|
878
|
+
if (sources.has(source)) throw new InvalidRivusProjectSpace(`project space ${declaration.id} contains duplicate Skill source: ${source}`);
|
|
879
|
+
sources.add(source);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
function validateRelativeProjectPath(value, owner) {
|
|
883
|
+
if (value.trim() === "" || isAbsoluteProjectPath(value) || value.includes("\0")) throw new InvalidRivusProjectSpace(`${owner} must be a non-empty relative path`);
|
|
884
|
+
}
|
|
885
|
+
function isAbsoluteProjectPath(value) {
|
|
886
|
+
return value.startsWith("/") || value.startsWith("\\") || /^[a-z]:[\\/]/i.test(value);
|
|
887
|
+
}
|
|
888
|
+
//#endregion
|
|
889
|
+
//#region src/modules/deployment-control/domain/manifest/deployment-manifest.ts
|
|
890
|
+
function parseRivusDeploymentManifest(value) {
|
|
789
891
|
const root = record(value, "manifest");
|
|
790
892
|
exactKeys(root, [
|
|
791
893
|
"agents",
|
|
@@ -823,15 +925,22 @@ function parseManifest(value) {
|
|
|
823
925
|
"pluginId",
|
|
824
926
|
"profileId",
|
|
825
927
|
"projectSpaceId",
|
|
928
|
+
"runtimeTools",
|
|
826
929
|
"skills",
|
|
827
930
|
"tools"
|
|
828
|
-
], `manifest.agents[${index}]`, [
|
|
931
|
+
], `manifest.agents[${index}]`, [
|
|
932
|
+
"memory",
|
|
933
|
+
"projectSpaceId",
|
|
934
|
+
"runtimeTools"
|
|
935
|
+
]);
|
|
829
936
|
const memory = agent.memory === void 0 ? void 0 : record(agent.memory, `manifest.agents[${index}].memory`);
|
|
830
937
|
if (memory) exactKeys(memory, ["scopes", "tool"], `manifest.agents[${index}].memory`);
|
|
831
938
|
const skills = record(agent.skills, `manifest.agents[${index}].skills`);
|
|
832
939
|
exactKeys(skills, ["allow"], `manifest.agents[${index}].skills`);
|
|
833
940
|
const tools = record(agent.tools, `manifest.agents[${index}].tools`);
|
|
834
941
|
exactKeys(tools, ["allow"], `manifest.agents[${index}].tools`);
|
|
942
|
+
const runtimeTools = agent.runtimeTools === void 0 ? void 0 : record(agent.runtimeTools, `manifest.agents[${index}].runtimeTools`);
|
|
943
|
+
if (runtimeTools) exactKeys(runtimeTools, ["allow"], `manifest.agents[${index}].runtimeTools`);
|
|
835
944
|
return Object.freeze({
|
|
836
945
|
agentId: string(agent.agentId, `manifest.agents[${index}].agentId`),
|
|
837
946
|
endpointIds: Object.freeze(array(agent.endpointIds, `manifest.agents[${index}].endpointIds`).map((item, itemIndex) => string(item, `manifest.agents[${index}].endpointIds[${itemIndex}]`))),
|
|
@@ -842,6 +951,7 @@ function parseManifest(value) {
|
|
|
842
951
|
pluginId: string(agent.pluginId, `manifest.agents[${index}].pluginId`),
|
|
843
952
|
profileId: string(agent.profileId, `manifest.agents[${index}].profileId`),
|
|
844
953
|
...agent.projectSpaceId === void 0 ? {} : { projectSpaceId: string(agent.projectSpaceId, `manifest.agents[${index}].projectSpaceId`) },
|
|
954
|
+
...runtimeTools ? { runtimeTools: Object.freeze({ allow: Object.freeze(uniqueRuntimeTools(array(runtimeTools.allow, `manifest.agents[${index}].runtimeTools.allow`).map((item, itemIndex) => runtimeToolId(item, `manifest.agents[${index}].runtimeTools.allow[${itemIndex}]`)), `manifest.agents[${index}].runtimeTools.allow`)) }) } : {},
|
|
845
955
|
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}]`))) }),
|
|
846
956
|
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}]`))) })
|
|
847
957
|
});
|
|
@@ -871,7 +981,7 @@ function parseManifest(value) {
|
|
|
871
981
|
return Object.freeze({
|
|
872
982
|
agentId: string(endpoint.agentId, `manifest.endpoints[${index}].agentId`),
|
|
873
983
|
baseUrl: string(endpoint.baseUrl, `manifest.endpoints[${index}].baseUrl`),
|
|
874
|
-
cardStreamLeaseMs: endpoint.cardStreamLeaseMs === void 0 ? DEFAULT_CARD_STREAM_LEASE_MS : positiveInteger(endpoint.cardStreamLeaseMs, `manifest.endpoints[${index}].cardStreamLeaseMs`),
|
|
984
|
+
cardStreamLeaseMs: endpoint.cardStreamLeaseMs === void 0 ? DEFAULT_CARD_STREAM_LEASE_MS$1 : positiveInteger(endpoint.cardStreamLeaseMs, `manifest.endpoints[${index}].cardStreamLeaseMs`),
|
|
875
985
|
credentialRef: string(endpoint.credentialRef, `manifest.endpoints[${index}].credentialRef`),
|
|
876
986
|
enabled: boolean(endpoint.enabled, `manifest.endpoints[${index}].enabled`),
|
|
877
987
|
...experimental ? { experimental: Object.freeze({ cotMessages: boolean(experimental.cotMessages, `manifest.endpoints[${index}].experimental.cotMessages`) }) } : {},
|
|
@@ -945,6 +1055,67 @@ function parseManifest(value) {
|
|
|
945
1055
|
projectSpaces: Object.freeze(projectSpaces)
|
|
946
1056
|
});
|
|
947
1057
|
}
|
|
1058
|
+
function validateRivusDeploymentManifest(manifest) {
|
|
1059
|
+
const projectSpaceIds = validateRivusProjectSpaceDeployments(manifest.projectSpaces ?? []);
|
|
1060
|
+
const pluginIds = /* @__PURE__ */ new Set();
|
|
1061
|
+
for (const plugin of manifest.plugins) {
|
|
1062
|
+
if (pluginIds.has(plugin.id)) throw new Error(`duplicate plugin declaration: ${plugin.id}`);
|
|
1063
|
+
validateModuleSpecifier(plugin.module);
|
|
1064
|
+
pluginIds.add(plugin.id);
|
|
1065
|
+
}
|
|
1066
|
+
const agentIds = /* @__PURE__ */ new Set();
|
|
1067
|
+
const agentById = /* @__PURE__ */ new Map();
|
|
1068
|
+
for (const agent of manifest.agents) {
|
|
1069
|
+
if (agentIds.has(agent.agentId)) throw new Error(`duplicate agent deployment: ${agent.agentId}`);
|
|
1070
|
+
agentIds.add(agent.agentId);
|
|
1071
|
+
if (!pluginIds.has(agent.pluginId)) throw new Error(`agent ${agent.agentId} references undeclared plugin: ${agent.pluginId}`);
|
|
1072
|
+
if (agent.projectSpaceId && !projectSpaceIds.has(agent.projectSpaceId)) throw new Error(`agent ${agent.agentId} references unknown project space: ${agent.projectSpaceId}`);
|
|
1073
|
+
agentById.set(agent.agentId, agent);
|
|
1074
|
+
}
|
|
1075
|
+
const endpointIds = /* @__PURE__ */ new Set();
|
|
1076
|
+
const sessionNamespaces = /* @__PURE__ */ new Set();
|
|
1077
|
+
for (const endpoint of manifest.endpoints) {
|
|
1078
|
+
if (endpointIds.has(endpoint.id)) throw new Error(`duplicate endpoint binding: ${endpoint.id}`);
|
|
1079
|
+
endpointIds.add(endpoint.id);
|
|
1080
|
+
if (sessionNamespaces.has(endpoint.sessionNamespace)) throw new Error(`duplicate endpoint session namespace: ${endpoint.sessionNamespace}`);
|
|
1081
|
+
sessionNamespaces.add(endpoint.sessionNamespace);
|
|
1082
|
+
const agent = agentById.get(endpoint.agentId);
|
|
1083
|
+
if (!agent) throw new Error(`endpoint ${endpoint.id} references unknown agent: ${endpoint.agentId}`);
|
|
1084
|
+
if (!agent.endpointIds.includes(endpoint.id)) throw new Error(`endpoint ${endpoint.id} is not declared by agent ${endpoint.agentId}`);
|
|
1085
|
+
}
|
|
1086
|
+
for (const agent of manifest.agents) for (const endpointId of agent.endpointIds) {
|
|
1087
|
+
const endpoint = manifest.endpoints.find(({ id }) => id === endpointId);
|
|
1088
|
+
if (!endpoint) throw new Error(`agent ${agent.agentId} references unknown endpoint: ${endpointId}`);
|
|
1089
|
+
if (endpoint.agentId !== agent.agentId) throw new Error(`endpoint ${endpointId} is bound to a different agent`);
|
|
1090
|
+
}
|
|
1091
|
+
const automationIds = /* @__PURE__ */ new Set();
|
|
1092
|
+
for (const automation of manifest.automations ?? []) {
|
|
1093
|
+
if (automationIds.has(automation.id)) throw new Error(`duplicate automation binding: ${automation.id}`);
|
|
1094
|
+
automationIds.add(automation.id);
|
|
1095
|
+
if (!agentById.has(automation.agentId)) throw new Error(`automation ${automation.id} references unknown agent: ${automation.agentId}`);
|
|
1096
|
+
const endpoint = manifest.endpoints.find(({ id }) => id === automation.delivery.endpointId);
|
|
1097
|
+
if (!endpoint) throw new Error(`automation ${automation.id} references unknown delivery endpoint: ${automation.delivery.endpointId}`);
|
|
1098
|
+
if (automation.enabled && !endpoint.enabled) throw new Error(`automation ${automation.id} delivery endpoint must be enabled`);
|
|
1099
|
+
}
|
|
1100
|
+
const defaultAgent = agentById.get(manifest.defaultAgentId);
|
|
1101
|
+
if (!defaultAgent) throw new Error(`default agent does not exist: ${manifest.defaultAgentId}`);
|
|
1102
|
+
const defaultEndpoint = manifest.endpoints.find(({ id }) => id === manifest.defaultEndpointId);
|
|
1103
|
+
if (!defaultEndpoint) throw new Error(`default endpoint does not exist: ${manifest.defaultEndpointId}`);
|
|
1104
|
+
if (defaultEndpoint.agentId !== defaultAgent.agentId) throw new Error("default endpoint is not bound to the default agent");
|
|
1105
|
+
if (!defaultEndpoint.enabled) throw new Error("default endpoint must be enabled");
|
|
1106
|
+
if (manifest.backgroundSessions) validateBackgroundSessions(manifest.backgroundSessions);
|
|
1107
|
+
}
|
|
1108
|
+
function validateBackgroundSessions(config) {
|
|
1109
|
+
if (config.required && !config.enabled) throw new Error("backgroundSessions.required requires backgroundSessions.enabled");
|
|
1110
|
+
if (config.leaseRenewalIntervalMs >= config.leaseMs) throw new Error("backgroundSessions.leaseRenewalIntervalMs must be shorter than leaseMs");
|
|
1111
|
+
if (config.stepTimeoutMs <= 0 || config.maxConcurrentSessions <= 0 || config.leaseMs <= 0) throw new Error("backgroundSessions durations and concurrency must be positive");
|
|
1112
|
+
if (config.retryBackoffMs <= 0 || config.sessionLifetimeMs <= 0 || config.maxConsecutiveFailures <= 0) throw new Error("backgroundSessions retry and lifetime limits must be positive");
|
|
1113
|
+
}
|
|
1114
|
+
function validateModuleSpecifier(moduleSpecifier) {
|
|
1115
|
+
const absolutePath = moduleSpecifier.startsWith("/") || moduleSpecifier.startsWith("\\\\") || /^[a-zA-Z]:[\\/]/.test(moduleSpecifier);
|
|
1116
|
+
if (moduleSpecifier.trim() === "" || absolutePath || /^[a-z][a-z+.-]*:/i.test(moduleSpecifier) || moduleSpecifier.includes("\0")) throw new Error(`invalid plugin module specifier: ${moduleSpecifier}`);
|
|
1117
|
+
if ((moduleSpecifier.startsWith("./") || moduleSpecifier.startsWith("../")) && moduleSpecifier.split(/[\\/]/).includes("..")) throw new Error(`plugin module escapes deployment root: ${moduleSpecifier}`);
|
|
1118
|
+
}
|
|
948
1119
|
function parseBackgroundSessions(value) {
|
|
949
1120
|
exactKeys(value, [
|
|
950
1121
|
"enabled",
|
|
@@ -967,14 +1138,14 @@ function parseBackgroundSessions(value) {
|
|
|
967
1138
|
]);
|
|
968
1139
|
return Object.freeze({
|
|
969
1140
|
enabled: boolean(value.enabled, "manifest.backgroundSessions.enabled"),
|
|
970
|
-
required: boolean(value.required, "manifest.backgroundSessions.required"),
|
|
971
|
-
stepTimeoutMs: positiveInteger(value.stepTimeoutMs ?? 3e5, "manifest.backgroundSessions.stepTimeoutMs"),
|
|
972
|
-
maxConcurrentSessions: positiveInteger(value.maxConcurrentSessions ?? 4, "manifest.backgroundSessions.maxConcurrentSessions"),
|
|
973
1141
|
leaseMs: positiveInteger(value.leaseMs ?? 3e4, "manifest.backgroundSessions.leaseMs"),
|
|
974
1142
|
leaseRenewalIntervalMs: positiveInteger(value.leaseRenewalIntervalMs ?? 1e4, "manifest.backgroundSessions.leaseRenewalIntervalMs"),
|
|
975
1143
|
maxConsecutiveFailures: positiveInteger(value.maxConsecutiveFailures ?? 3, "manifest.backgroundSessions.maxConsecutiveFailures"),
|
|
1144
|
+
maxConcurrentSessions: positiveInteger(value.maxConcurrentSessions ?? 4, "manifest.backgroundSessions.maxConcurrentSessions"),
|
|
1145
|
+
required: boolean(value.required, "manifest.backgroundSessions.required"),
|
|
976
1146
|
retryBackoffMs: positiveInteger(value.retryBackoffMs ?? 3e4, "manifest.backgroundSessions.retryBackoffMs"),
|
|
977
|
-
sessionLifetimeMs: positiveInteger(value.sessionLifetimeMs ?? 864e5, "manifest.backgroundSessions.sessionLifetimeMs")
|
|
1147
|
+
sessionLifetimeMs: positiveInteger(value.sessionLifetimeMs ?? 864e5, "manifest.backgroundSessions.sessionLifetimeMs"),
|
|
1148
|
+
stepTimeoutMs: positiveInteger(value.stepTimeoutMs ?? 3e5, "manifest.backgroundSessions.stepTimeoutMs")
|
|
978
1149
|
});
|
|
979
1150
|
}
|
|
980
1151
|
function record(value, path) {
|
|
@@ -1009,6 +1180,19 @@ function progressDisplay(value, path) {
|
|
|
1009
1180
|
if (value !== "hidden" && value !== "collapsed" && value !== "expanded") throw new Error(`${path} must be hidden, collapsed, or expanded`);
|
|
1010
1181
|
return value;
|
|
1011
1182
|
}
|
|
1183
|
+
function runtimeToolId(value, path) {
|
|
1184
|
+
const id = string(value, path);
|
|
1185
|
+
if (!isRivusRuntimeToolId(id)) throw new Error(`${path} must be read, bash, edit, write, grep, find, or ls`);
|
|
1186
|
+
return id;
|
|
1187
|
+
}
|
|
1188
|
+
function uniqueRuntimeTools(ids, path) {
|
|
1189
|
+
const result = /* @__PURE__ */ new Set();
|
|
1190
|
+
for (const id of ids) {
|
|
1191
|
+
if (result.has(id)) throw new Error(`${path} contains duplicate Runtime Tool: ${id}`);
|
|
1192
|
+
result.add(id);
|
|
1193
|
+
}
|
|
1194
|
+
return [...result];
|
|
1195
|
+
}
|
|
1012
1196
|
function automationTargetType(value, path) {
|
|
1013
1197
|
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`);
|
|
1014
1198
|
return value;
|
|
@@ -1020,353 +1204,708 @@ function exactKeys(value, allowed, path, optional = []) {
|
|
|
1020
1204
|
if (missing) throw new Error(`${path} is missing required field: ${missing}`);
|
|
1021
1205
|
}
|
|
1022
1206
|
//#endregion
|
|
1023
|
-
//#region src/
|
|
1024
|
-
|
|
1025
|
-
return
|
|
1026
|
-
}
|
|
1027
|
-
async function resolveNodeRivusPluginModulePath(request) {
|
|
1028
|
-
const deploymentRoot = await realpath(request.deploymentRoot);
|
|
1029
|
-
const resolvedRealpath = await realpath(createRequire(join(deploymentRoot, "package.json")).resolve(request.module));
|
|
1030
|
-
if (!isWithin(deploymentRoot, resolvedRealpath)) throw new Error(`plugin module ${request.module} resolves outside deployment root: ${resolvedRealpath}`);
|
|
1031
|
-
return resolvedRealpath;
|
|
1207
|
+
//#region src/modules/deployment-control/application/failure/deployment-failure.ts
|
|
1208
|
+
function formatDeploymentFailure(failure) {
|
|
1209
|
+
return failure instanceof Error ? failure.message : String(failure);
|
|
1032
1210
|
}
|
|
1033
|
-
function
|
|
1034
|
-
|
|
1035
|
-
return child === "" || !child.startsWith(`..${sep}`) && child !== ".." && !isAbsolute(child);
|
|
1211
|
+
function toDeploymentFailure(failure) {
|
|
1212
|
+
return failure instanceof Error ? failure : new Error(String(failure));
|
|
1036
1213
|
}
|
|
1037
1214
|
//#endregion
|
|
1038
|
-
//#region src/
|
|
1039
|
-
var
|
|
1040
|
-
|
|
1041
|
-
message;
|
|
1042
|
-
_tag = "RivusDaemonConfigError";
|
|
1043
|
-
constructor(variable, message) {
|
|
1044
|
-
this.variable = variable;
|
|
1045
|
-
this.message = message;
|
|
1046
|
-
}
|
|
1215
|
+
//#region src/modules/deployment-control/domain/lifecycle/deployment-lifecycle.ts
|
|
1216
|
+
var InvalidDeploymentLifecycleTransition = class extends Error {
|
|
1217
|
+
name = "InvalidDeploymentLifecycleTransition";
|
|
1047
1218
|
};
|
|
1048
|
-
const
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
"
|
|
1058
|
-
]
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
function readUtf8File(path) {
|
|
1092
|
-
return readFile(path, "utf8");
|
|
1093
|
-
}
|
|
1094
|
-
function optionalPiApiKey(env, readTextFile) {
|
|
1095
|
-
const inlineApiKey = optional(env.PI_API_KEY);
|
|
1096
|
-
const apiKeyFile = optional(env.PI_API_KEY_FILE);
|
|
1097
|
-
if (inlineApiKey && apiKeyFile) return Effect.fail(new RivusDaemonConfigError("PI_API_KEY", "PI_API_KEY and PI_API_KEY_FILE cannot both be set"));
|
|
1098
|
-
if (inlineApiKey) return Effect.succeed(inlineApiKey);
|
|
1099
|
-
if (!apiKeyFile) return Effect.succeed(void 0);
|
|
1100
|
-
return Effect.tryPromise({
|
|
1101
|
-
try: async () => readTextFile(apiKeyFile),
|
|
1102
|
-
catch: (error) => new RivusDaemonConfigError("PI_API_KEY_FILE", `PI_API_KEY_FILE could not be read: ${formatConfigError(error)}`)
|
|
1103
|
-
}).pipe(Effect.flatMap((contents) => {
|
|
1104
|
-
const apiKey = optional(contents);
|
|
1105
|
-
return apiKey ? Effect.succeed(apiKey) : Effect.fail(new RivusDaemonConfigError("PI_API_KEY_FILE", "PI_API_KEY_FILE must not be empty"));
|
|
1106
|
-
}));
|
|
1107
|
-
}
|
|
1108
|
-
function formatConfigError(error) {
|
|
1109
|
-
return error instanceof Error ? error.message : String(error);
|
|
1110
|
-
}
|
|
1111
|
-
function optionalPositiveInteger(value, variable, fallback) {
|
|
1112
|
-
const normalized = optional(value);
|
|
1113
|
-
if (!normalized) return Effect.succeed(fallback);
|
|
1114
|
-
if (/^[1-9]\d*$/.test(normalized)) return Effect.succeed(Number(normalized));
|
|
1115
|
-
return Effect.fail(new RivusDaemonConfigError(variable, `${variable} must be a positive integer`));
|
|
1116
|
-
}
|
|
1117
|
-
function required(env, variable) {
|
|
1118
|
-
const value = optional(env[variable]);
|
|
1119
|
-
if (value) return Effect.succeed(value);
|
|
1120
|
-
return Effect.fail(new RivusDaemonConfigError(variable, `${variable} is required`));
|
|
1121
|
-
}
|
|
1122
|
-
function optionalThinkingLevel(value) {
|
|
1123
|
-
const normalized = optional(value);
|
|
1124
|
-
if (!normalized) return Effect.succeed(void 0);
|
|
1125
|
-
if (THINKING_LEVELS$1.has(normalized)) return Effect.succeed(normalized);
|
|
1126
|
-
return Effect.fail(new RivusDaemonConfigError("PI_THINKING_LEVEL", "PI_THINKING_LEVEL must be one of off, minimal, low, medium, high, xhigh"));
|
|
1219
|
+
const componentTransitions = Object.freeze({
|
|
1220
|
+
"cleanup-required": ["cleanup-required", "stopping"],
|
|
1221
|
+
degraded: ["degraded", "stopping"],
|
|
1222
|
+
disabled: ["disabled"],
|
|
1223
|
+
running: [
|
|
1224
|
+
"degraded",
|
|
1225
|
+
"running",
|
|
1226
|
+
"stopping"
|
|
1227
|
+
],
|
|
1228
|
+
starting: ["degraded", "running"],
|
|
1229
|
+
stopped: ["starting", "stopped"],
|
|
1230
|
+
stopping: [
|
|
1231
|
+
"cleanup-required",
|
|
1232
|
+
"disabled",
|
|
1233
|
+
"stopped"
|
|
1234
|
+
]
|
|
1235
|
+
});
|
|
1236
|
+
const controlTransitions = Object.freeze({
|
|
1237
|
+
"cleanup-required": ["cleanup-required", "stopping"],
|
|
1238
|
+
degraded: ["degraded", "stopping"],
|
|
1239
|
+
running: [
|
|
1240
|
+
"degraded",
|
|
1241
|
+
"running",
|
|
1242
|
+
"stopping"
|
|
1243
|
+
],
|
|
1244
|
+
starting: ["degraded", "running"],
|
|
1245
|
+
stopped: [
|
|
1246
|
+
"starting",
|
|
1247
|
+
"stopped",
|
|
1248
|
+
"stopping"
|
|
1249
|
+
],
|
|
1250
|
+
stopping: ["cleanup-required", "stopped"]
|
|
1251
|
+
});
|
|
1252
|
+
function transitionDeploymentComponentLifecycle(current, next) {
|
|
1253
|
+
if (!componentTransitions[current].includes(next)) throw new InvalidDeploymentLifecycleTransition(`cannot transition deployment component from ${current} to ${next}`);
|
|
1254
|
+
return next;
|
|
1255
|
+
}
|
|
1256
|
+
function transitionDeploymentControlLifecycle(current, next) {
|
|
1257
|
+
if (!controlTransitions[current].includes(next)) throw new InvalidDeploymentLifecycleTransition(`cannot transition Deployment Control from ${current} to ${next}`);
|
|
1258
|
+
return next;
|
|
1259
|
+
}
|
|
1260
|
+
function isRequiredDeploymentComponentReady(input) {
|
|
1261
|
+
return !input.enabled || !input.required || input.lifecycle === "running" && input.running;
|
|
1127
1262
|
}
|
|
1128
1263
|
//#endregion
|
|
1129
|
-
//#region src/
|
|
1130
|
-
var
|
|
1131
|
-
|
|
1132
|
-
super(message);
|
|
1133
|
-
this.name = "OpenClawEnvImportError";
|
|
1134
|
-
}
|
|
1264
|
+
//#region src/modules/deployment-control/application/lifecycle/deployment-control.ts
|
|
1265
|
+
var RivusDeploymentDaemonLifecycleError = class extends Error {
|
|
1266
|
+
name = "RivusDeploymentDaemonLifecycleError";
|
|
1135
1267
|
};
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
"
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
"
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
function
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
const
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1268
|
+
var RivusDeploymentReadinessError = class extends Error {
|
|
1269
|
+
endpointIds;
|
|
1270
|
+
name = "RivusDeploymentReadinessError";
|
|
1271
|
+
constructor(endpointIds) {
|
|
1272
|
+
super(`required endpoint startup failed: ${endpointIds.join(", ")}`);
|
|
1273
|
+
this.endpointIds = endpointIds;
|
|
1274
|
+
}
|
|
1275
|
+
};
|
|
1276
|
+
var RivusDeploymentAutomationReadinessError = class extends Error {
|
|
1277
|
+
automationIds;
|
|
1278
|
+
name = "RivusDeploymentAutomationReadinessError";
|
|
1279
|
+
constructor(automationIds) {
|
|
1280
|
+
super(`required automation startup failed: ${automationIds.join(", ")}`);
|
|
1281
|
+
this.automationIds = automationIds;
|
|
1282
|
+
}
|
|
1283
|
+
};
|
|
1284
|
+
var RivusDeploymentBackgroundSessionReadinessError = class extends Error {
|
|
1285
|
+
name = "RivusDeploymentBackgroundSessionReadinessError";
|
|
1286
|
+
constructor() {
|
|
1287
|
+
super("required Background Session startup failed");
|
|
1288
|
+
}
|
|
1289
|
+
};
|
|
1290
|
+
function createRivusDeploymentControl(input) {
|
|
1291
|
+
return Effect.gen(function* () {
|
|
1292
|
+
const deployment = input.deployment;
|
|
1293
|
+
if ((deployment.manifest.projectSpaces?.length ?? 0) > 0 && !input.projectSpaceResolver) return yield* Effect.fail(new RivusDeploymentDaemonLifecycleError("deployment bootstrap does not provide a Project Space resolver"));
|
|
1294
|
+
const projectSpaces = /* @__PURE__ */ new Map();
|
|
1295
|
+
for (const declaration of deployment.manifest.projectSpaces ?? []) {
|
|
1296
|
+
const resolved = yield* input.projectSpaceResolver.resolve({
|
|
1297
|
+
declaration,
|
|
1298
|
+
deploymentRoot: input.deploymentRoot
|
|
1299
|
+
}).pipe(Effect.mapError(toDeploymentFailure));
|
|
1300
|
+
projectSpaces.set(resolved.id, resolved);
|
|
1301
|
+
}
|
|
1302
|
+
const definitions = yield* Effect.try({
|
|
1303
|
+
try: () => new Map(deployment.definitions.map((definition) => {
|
|
1304
|
+
const resolved = bindResolvedProjectSpaceRevision(definition, projectSpaces);
|
|
1305
|
+
return [resolved.agentId, resolved];
|
|
1306
|
+
})),
|
|
1307
|
+
catch: toDeploymentFailure
|
|
1308
|
+
});
|
|
1309
|
+
const automationDefinitions = yield* Effect.try({
|
|
1310
|
+
try: () => new Map(deployment.automationDefinitions.map((definition) => [definition.id, Object.freeze({
|
|
1311
|
+
...definition,
|
|
1312
|
+
runtimeDefinition: bindResolvedProjectSpaceRevision(definition.runtimeDefinition, projectSpaces)
|
|
1313
|
+
})])),
|
|
1314
|
+
catch: toDeploymentFailure
|
|
1315
|
+
});
|
|
1316
|
+
const effectiveDeployment = Object.freeze({
|
|
1317
|
+
...deployment,
|
|
1318
|
+
agents: Object.freeze(deployment.agents.map((agent) => {
|
|
1319
|
+
if (!agent.definition) return agent;
|
|
1320
|
+
const definition = definitions.get(agent.agentId);
|
|
1321
|
+
return definition ? Object.freeze({
|
|
1322
|
+
...agent,
|
|
1323
|
+
definition
|
|
1324
|
+
}) : agent;
|
|
1325
|
+
})),
|
|
1326
|
+
automationDefinitions: Object.freeze([...automationDefinitions.values()]),
|
|
1327
|
+
definitions: Object.freeze([...definitions.values()])
|
|
1328
|
+
});
|
|
1329
|
+
const backgroundConfig = deployment.manifest.backgroundSessions;
|
|
1330
|
+
const backgroundDefinitions = /* @__PURE__ */ new Map();
|
|
1331
|
+
if (backgroundConfig?.enabled) for (const agent of effectiveDeployment.agents) {
|
|
1332
|
+
if (agent.status !== "enabled" || !agent.definition) continue;
|
|
1333
|
+
const narrowed = yield* Effect.try({
|
|
1334
|
+
try: () => narrowBackgroundSessionDefinition(definitions.get(agent.agentId)),
|
|
1335
|
+
catch: toDeploymentFailure
|
|
1336
|
+
});
|
|
1337
|
+
backgroundDefinitions.set(agent.agentId, narrowed);
|
|
1338
|
+
}
|
|
1339
|
+
const agentStatuses = new Map(effectiveDeployment.agents.map((agent) => [agent.agentId, agent]));
|
|
1340
|
+
const endpointSlots = deployment.manifest.endpoints.map((definition) => {
|
|
1341
|
+
return createSlot(definition, agentStatuses.get(definition.agentId)?.status === "enabled");
|
|
1342
|
+
});
|
|
1343
|
+
const automationSlots = (deployment.manifest.automations ?? []).map((definition) => {
|
|
1344
|
+
const agentEnabled = agentStatuses.get(definition.agentId)?.status === "enabled";
|
|
1345
|
+
const resolvedDefinition = automationDefinitions.get(definition.id);
|
|
1346
|
+
return {
|
|
1347
|
+
...createSlot(definition, agentEnabled && resolvedDefinition !== void 0),
|
|
1348
|
+
agentEnabled,
|
|
1349
|
+
...resolvedDefinition ? { resolvedDefinition } : {}
|
|
1350
|
+
};
|
|
1351
|
+
});
|
|
1352
|
+
const backgroundSessionSlot = backgroundConfig ? createSlot(backgroundConfig, backgroundDefinitions.size > 0) : void 0;
|
|
1353
|
+
const host = yield* input.agentHostFactory.create({
|
|
1354
|
+
automations: automationSlots.filter((slot) => slot.definition.enabled && slot.agentEnabled && slot.resolvedDefinition !== void 0).map((slot) => ({
|
|
1355
|
+
definition: slot.resolvedDefinition.runtimeDefinition,
|
|
1356
|
+
id: slot.definition.id
|
|
1357
|
+
})),
|
|
1358
|
+
backgroundSessions: [...backgroundDefinitions].map(([agentId, definition]) => ({
|
|
1359
|
+
agentId,
|
|
1360
|
+
definition
|
|
1361
|
+
})),
|
|
1362
|
+
definitions: [...definitions.values()],
|
|
1363
|
+
endpoints: endpointSlots.filter((slot) => slot.definition.enabled && slot.agentEnabled).map((slot) => ({
|
|
1364
|
+
agentId: slot.definition.agentId,
|
|
1365
|
+
id: slot.definition.id
|
|
1366
|
+
})),
|
|
1367
|
+
...input.initialInstanceRecords ? { initialInstanceRecords: input.initialInstanceRecords } : {},
|
|
1368
|
+
runtimeFactory: { create: (instance) => {
|
|
1369
|
+
const definition = instance.binding.kind === "automation" ? automationDefinitions.get(instance.binding.automationId)?.runtimeDefinition : instance.binding.kind === "background-session" ? backgroundDefinitions.get(instance.agentId) : definitions.get(instance.agentId);
|
|
1370
|
+
if (!definition) return Effect.fail(new RivusDeploymentDaemonLifecycleError(`runtime instance references unknown agent: ${instance.agentId}`));
|
|
1371
|
+
const projectSpace = definition.projectSpaceId ? projectSpaces.get(definition.projectSpaceId) : void 0;
|
|
1372
|
+
return input.runtimeFactory.create({
|
|
1373
|
+
...instance,
|
|
1374
|
+
catalog: effectiveDeployment.catalog,
|
|
1375
|
+
definition,
|
|
1376
|
+
...projectSpace ? { projectSpace } : {}
|
|
1377
|
+
});
|
|
1378
|
+
} }
|
|
1379
|
+
}).pipe(Effect.mapError(toDeploymentFailure));
|
|
1380
|
+
return makeDeploymentControl({
|
|
1381
|
+
...input.automationFactory ? { automationFactory: input.automationFactory } : {},
|
|
1382
|
+
automationSlots,
|
|
1383
|
+
...input.backgroundSessionFactory ? { backgroundSessionFactory: input.backgroundSessionFactory } : {},
|
|
1384
|
+
...backgroundSessionSlot ? { backgroundSessionSlot } : {},
|
|
1385
|
+
definitions,
|
|
1386
|
+
deployment: effectiveDeployment,
|
|
1387
|
+
endpointFactory: input.endpointFactory,
|
|
1388
|
+
endpointSlots,
|
|
1389
|
+
host
|
|
1390
|
+
});
|
|
1391
|
+
});
|
|
1248
1392
|
}
|
|
1249
|
-
function
|
|
1250
|
-
const
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
]);
|
|
1256
|
-
if (!Array.isArray(providerModels)) return [];
|
|
1257
|
-
const model = providerModels.map(optionalRecord).find((candidate) => optionalString(candidate?.id) === modelId);
|
|
1258
|
-
if (!model) return [];
|
|
1259
|
-
return [
|
|
1260
|
-
model.thinkingLevel,
|
|
1261
|
-
model.thinkLevel,
|
|
1262
|
-
model.reasoningLevel,
|
|
1263
|
-
readPath(model, ["reasoning", "level"]),
|
|
1264
|
-
readPath(model, ["reasoning", "thinkingLevel"]),
|
|
1265
|
-
readPath(model, ["reasoning", "thinkLevel"])
|
|
1393
|
+
function makeDeploymentControl(input) {
|
|
1394
|
+
const endpointById = new Map(input.endpointSlots.map((slot) => [slot.definition.id, slot]));
|
|
1395
|
+
const allSlots = [
|
|
1396
|
+
...input.endpointSlots,
|
|
1397
|
+
...input.automationSlots,
|
|
1398
|
+
...input.backgroundSessionSlot ? [input.backgroundSessionSlot] : []
|
|
1266
1399
|
];
|
|
1400
|
+
let lifecycle = "stopped";
|
|
1401
|
+
const refreshObservedState = () => {
|
|
1402
|
+
let degraded = false;
|
|
1403
|
+
for (const slot of allSlots) {
|
|
1404
|
+
if (slot.lifecycle !== "running") continue;
|
|
1405
|
+
const observed = observeRunning(slot);
|
|
1406
|
+
if (!observed.running) {
|
|
1407
|
+
slot.lifecycle = transitionDeploymentComponentLifecycle(slot.lifecycle, "degraded");
|
|
1408
|
+
slot.error = observed.error ?? "component stopped running after startup";
|
|
1409
|
+
degraded = true;
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
if (degraded && lifecycle === "running") lifecycle = transitionDeploymentControlLifecycle(lifecycle, "degraded");
|
|
1413
|
+
};
|
|
1414
|
+
const canRunIntake = () => {
|
|
1415
|
+
refreshObservedState();
|
|
1416
|
+
return lifecycle === "running" || lifecycle === "degraded";
|
|
1417
|
+
};
|
|
1418
|
+
const handleEndpoint = (endpointId, request) => Effect.suspend(() => {
|
|
1419
|
+
const slot = endpointById.get(endpointId);
|
|
1420
|
+
if (!slot) return Effect.fail(new RivusDeploymentDaemonLifecycleError(`unknown endpoint: ${endpointId}`));
|
|
1421
|
+
const observed = observeRunning(slot);
|
|
1422
|
+
if (!canRunIntake() || slot.lifecycle !== "running" || !observed.running) return Effect.fail(new RivusDeploymentDaemonLifecycleError(`endpoint ${endpointId} cannot accept intake while Deployment Control is ${lifecycle} and endpoint is ${slot.lifecycle}`));
|
|
1423
|
+
return input.host.handleEndpoint(endpointId, request);
|
|
1424
|
+
});
|
|
1425
|
+
const isReady = () => allSlots.every((slot) => isSlotReady(slot));
|
|
1426
|
+
const readinessFailure = () => {
|
|
1427
|
+
const endpointIds = input.endpointSlots.filter((slot) => slot.definition.enabled && slot.definition.required && !isSlotReady(slot)).map((slot) => slot.definition.id);
|
|
1428
|
+
if (endpointIds.length > 0) return new RivusDeploymentReadinessError(Object.freeze(endpointIds));
|
|
1429
|
+
const automationIds = input.automationSlots.filter((slot) => slot.definition.enabled && slot.definition.required && !isSlotReady(slot)).map((slot) => slot.definition.id);
|
|
1430
|
+
if (automationIds.length > 0) return new RivusDeploymentAutomationReadinessError(Object.freeze(automationIds));
|
|
1431
|
+
if (input.backgroundSessionSlot?.definition.enabled && input.backgroundSessionSlot.definition.required && !isSlotReady(input.backgroundSessionSlot)) return new RivusDeploymentBackgroundSessionReadinessError();
|
|
1432
|
+
};
|
|
1433
|
+
const status = () => {
|
|
1434
|
+
refreshObservedState();
|
|
1435
|
+
return Object.freeze({
|
|
1436
|
+
agents: input.deployment.agents,
|
|
1437
|
+
automations: Object.freeze(input.automationSlots.map((slot) => ({
|
|
1438
|
+
...componentStatus(slot),
|
|
1439
|
+
agentId: slot.definition.agentId,
|
|
1440
|
+
automationId: slot.definition.id
|
|
1441
|
+
}))),
|
|
1442
|
+
...input.backgroundSessionSlot ? { backgroundSessions: backgroundStatus(input.backgroundSessionSlot) } : {},
|
|
1443
|
+
defaultAgentId: input.deployment.manifest.defaultAgentId,
|
|
1444
|
+
defaultEndpointId: input.deployment.manifest.defaultEndpointId,
|
|
1445
|
+
endpoints: Object.freeze(input.endpointSlots.map((slot) => ({
|
|
1446
|
+
...componentStatus(slot),
|
|
1447
|
+
agentId: slot.definition.agentId,
|
|
1448
|
+
endpointId: slot.definition.id
|
|
1449
|
+
}))),
|
|
1450
|
+
lifecycle,
|
|
1451
|
+
plugins: input.deployment.plugins,
|
|
1452
|
+
ready: isReady(),
|
|
1453
|
+
running: lifecycle === "running" || lifecycle === "degraded"
|
|
1454
|
+
});
|
|
1455
|
+
};
|
|
1456
|
+
return {
|
|
1457
|
+
deployment: input.deployment,
|
|
1458
|
+
handleDefault: (request) => handleEndpoint(input.deployment.manifest.defaultEndpointId, request),
|
|
1459
|
+
handleEndpoint,
|
|
1460
|
+
runDefaultAgent: (request) => input.host.handleEndpoint(input.deployment.manifest.defaultEndpointId, request),
|
|
1461
|
+
running: canRunIntake,
|
|
1462
|
+
start: () => Effect.suspend(() => {
|
|
1463
|
+
refreshObservedState();
|
|
1464
|
+
if (lifecycle === "running") return Effect.void;
|
|
1465
|
+
if (lifecycle === "degraded") {
|
|
1466
|
+
const failure = readinessFailure();
|
|
1467
|
+
return failure ? Effect.fail(failure) : Effect.void;
|
|
1468
|
+
}
|
|
1469
|
+
if (lifecycle !== "stopped") return Effect.fail(new RivusDeploymentDaemonLifecycleError(`cannot start deployment daemon while ${lifecycle}`));
|
|
1470
|
+
lifecycle = transitionDeploymentControlLifecycle(lifecycle, "starting");
|
|
1471
|
+
return Effect.gen(function* () {
|
|
1472
|
+
let degraded = input.deployment.plugins.some(({ status: pluginStatus }) => pluginStatus === "failed");
|
|
1473
|
+
for (const slot of input.endpointSlots) degraded = (yield* startSlot(slot, slot.agentEnabled, `endpoint ${slot.definition.id}`, () => Effect.gen(function* () {
|
|
1474
|
+
const instance = yield* input.host.resolveEndpoint(slot.definition.id);
|
|
1475
|
+
return yield* input.endpointFactory.create({
|
|
1476
|
+
agentId: slot.definition.agentId,
|
|
1477
|
+
cancel: (request) => input.host.cancelEndpoint(slot.definition.id, request),
|
|
1478
|
+
definition: slot.definition,
|
|
1479
|
+
endpointId: slot.definition.id,
|
|
1480
|
+
handle: (request) => handleEndpoint(slot.definition.id, request),
|
|
1481
|
+
instanceId: instance.instanceId,
|
|
1482
|
+
...input.definitions.get(slot.definition.agentId)?.projectSpaceId ? { projectSpaceId: input.definitions.get(slot.definition.agentId).projectSpaceId } : {},
|
|
1483
|
+
steer: (request) => input.host.steerEndpoint(slot.definition.id, request)
|
|
1484
|
+
});
|
|
1485
|
+
}))) || degraded;
|
|
1486
|
+
for (const slot of input.automationSlots) {
|
|
1487
|
+
const resolvedDefinition = slot.resolvedDefinition;
|
|
1488
|
+
const deliverySlot = endpointById.get(slot.definition.delivery.endpointId);
|
|
1489
|
+
const deliveryRunning = observeRunning(deliverySlot).running && deliverySlot.lifecycle === "running";
|
|
1490
|
+
degraded = (yield* startSlot(slot, slot.agentEnabled && resolvedDefinition !== void 0 && deliveryRunning, `automation ${slot.definition.id}`, () => Effect.gen(function* () {
|
|
1491
|
+
if (!input.automationFactory) return yield* Effect.fail(new RivusDeploymentDaemonLifecycleError("deployment bootstrap does not provide Automation adapters"));
|
|
1492
|
+
if (!resolvedDefinition) return yield* Effect.fail(new RivusDeploymentDaemonLifecycleError(`automation definition not resolved: ${slot.definition.id}`));
|
|
1493
|
+
const instance = yield* input.host.resolveAutomation(slot.definition.id);
|
|
1494
|
+
return yield* input.automationFactory.create({
|
|
1495
|
+
automationId: slot.definition.id,
|
|
1496
|
+
definition: resolvedDefinition,
|
|
1497
|
+
deliveryEndpoint: deliverySlot.definition,
|
|
1498
|
+
instanceId: instance.instanceId,
|
|
1499
|
+
run: (request) => input.host.handleAutomation(slot.definition.id, {
|
|
1500
|
+
invocation: {
|
|
1501
|
+
allowedActorOpenIds: [],
|
|
1502
|
+
automationId: slot.definition.id,
|
|
1503
|
+
endpointId: slot.definition.delivery.endpointId,
|
|
1504
|
+
kind: "automation",
|
|
1505
|
+
sourceMessageId: request.tickId,
|
|
1506
|
+
tenantKey: "automation",
|
|
1507
|
+
tickId: request.tickId
|
|
1508
|
+
},
|
|
1509
|
+
sessionKey: request.sessionKey,
|
|
1510
|
+
text: request.text
|
|
1511
|
+
})
|
|
1512
|
+
});
|
|
1513
|
+
}))) || degraded;
|
|
1514
|
+
}
|
|
1515
|
+
if (input.backgroundSessionSlot) {
|
|
1516
|
+
const slot = input.backgroundSessionSlot;
|
|
1517
|
+
degraded = (yield* startSlot(slot, slot.agentEnabled, "Background Session", () => Effect.gen(function* () {
|
|
1518
|
+
if (!input.backgroundSessionFactory) return yield* Effect.fail(new RivusDeploymentDaemonLifecycleError("deployment bootstrap does not provide Background Session adapters"));
|
|
1519
|
+
return yield* input.backgroundSessionFactory.create({
|
|
1520
|
+
agentIds: [...input.definitions.keys()].filter((agentId) => input.deployment.agents.some((agent) => agent.agentId === agentId && agent.status === "enabled")),
|
|
1521
|
+
cancel: (request) => input.host.cancelBackgroundSession(request.agentId, request),
|
|
1522
|
+
config: slot.definition,
|
|
1523
|
+
run: (request) => input.host.handleBackgroundSession(request.agentId, {
|
|
1524
|
+
...request.invocation ? { invocation: request.invocation } : {},
|
|
1525
|
+
...request.onUpdate ? { onUpdate: request.onUpdate } : {},
|
|
1526
|
+
sessionKey: request.sessionKey,
|
|
1527
|
+
text: request.text
|
|
1528
|
+
})
|
|
1529
|
+
});
|
|
1530
|
+
}))) || degraded;
|
|
1531
|
+
}
|
|
1532
|
+
lifecycle = transitionDeploymentControlLifecycle(lifecycle, degraded ? "degraded" : "running");
|
|
1533
|
+
const failure = readinessFailure();
|
|
1534
|
+
if (failure) return yield* Effect.fail(failure);
|
|
1535
|
+
});
|
|
1536
|
+
}),
|
|
1537
|
+
status,
|
|
1538
|
+
stop: () => Effect.suspend(() => {
|
|
1539
|
+
if (lifecycle !== "stopped" && lifecycle !== "running" && lifecycle !== "degraded" && lifecycle !== "cleanup-required") return Effect.fail(new RivusDeploymentDaemonLifecycleError(`cannot stop deployment daemon while ${lifecycle}`));
|
|
1540
|
+
lifecycle = transitionDeploymentControlLifecycle(lifecycle, "stopping");
|
|
1541
|
+
return Effect.gen(function* () {
|
|
1542
|
+
const errors = [];
|
|
1543
|
+
yield* stopSlots(input.automationSlots, errors);
|
|
1544
|
+
if (input.backgroundSessionSlot) yield* stopSlots([input.backgroundSessionSlot], errors);
|
|
1545
|
+
yield* stopSlots(input.endpointSlots, errors);
|
|
1546
|
+
const hostExit = yield* Effect.exit(input.host.dispose());
|
|
1547
|
+
if (Exit.isFailure(hostExit)) errors.push(Cause.squash(hostExit.cause));
|
|
1548
|
+
lifecycle = transitionDeploymentControlLifecycle(lifecycle, errors.length === 0 ? "stopped" : "cleanup-required");
|
|
1549
|
+
if (errors.length === 1) return yield* Effect.fail(errors[0]);
|
|
1550
|
+
if (errors.length > 1) return yield* Effect.fail(new AggregateError(errors, "deployment daemon cleanup failed"));
|
|
1551
|
+
});
|
|
1552
|
+
})
|
|
1553
|
+
};
|
|
1267
1554
|
}
|
|
1268
|
-
function
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1555
|
+
function createSlot(definition, agentEnabled) {
|
|
1556
|
+
return {
|
|
1557
|
+
agentEnabled,
|
|
1558
|
+
definition,
|
|
1559
|
+
...!definition.enabled ? { lifecycle: "disabled" } : agentEnabled ? { lifecycle: "stopped" } : {
|
|
1560
|
+
error: "component is unavailable",
|
|
1561
|
+
lifecycle: "degraded"
|
|
1562
|
+
}
|
|
1563
|
+
};
|
|
1274
1564
|
}
|
|
1275
|
-
function
|
|
1276
|
-
return
|
|
1565
|
+
function startSlot(slot, available, label, create) {
|
|
1566
|
+
return Effect.gen(function* () {
|
|
1567
|
+
if (!slot.definition.enabled) {
|
|
1568
|
+
slot.lifecycle = "disabled";
|
|
1569
|
+
return false;
|
|
1570
|
+
}
|
|
1571
|
+
if (!available) {
|
|
1572
|
+
slot.lifecycle = "degraded";
|
|
1573
|
+
slot.error = `${label} is unavailable`;
|
|
1574
|
+
return true;
|
|
1575
|
+
}
|
|
1576
|
+
if (slot.lifecycle !== "stopped") return yield* Effect.fail(new RivusDeploymentDaemonLifecycleError(`cannot start ${label} while ${slot.lifecycle}`));
|
|
1577
|
+
slot.lifecycle = transitionDeploymentComponentLifecycle(slot.lifecycle, "starting");
|
|
1578
|
+
delete slot.error;
|
|
1579
|
+
const started = yield* Effect.gen(function* () {
|
|
1580
|
+
slot.adapter ??= yield* create();
|
|
1581
|
+
yield* slot.adapter.start();
|
|
1582
|
+
if (!(yield* Effect.try({
|
|
1583
|
+
try: () => slot.adapter.running(),
|
|
1584
|
+
catch: toDeploymentFailure
|
|
1585
|
+
}))) return yield* Effect.fail(/* @__PURE__ */ new Error(`${label} start completed but the adapter is not running`));
|
|
1586
|
+
}).pipe(Effect.exit);
|
|
1587
|
+
if (Exit.isFailure(started)) {
|
|
1588
|
+
slot.error = formatDeploymentFailure(Cause.squash(started.cause));
|
|
1589
|
+
slot.lifecycle = transitionDeploymentComponentLifecycle(slot.lifecycle, "degraded");
|
|
1590
|
+
return true;
|
|
1591
|
+
}
|
|
1592
|
+
slot.lifecycle = transitionDeploymentComponentLifecycle(slot.lifecycle, "running");
|
|
1593
|
+
return false;
|
|
1594
|
+
});
|
|
1277
1595
|
}
|
|
1278
|
-
function
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1596
|
+
function stopSlots(slots, errors) {
|
|
1597
|
+
return Effect.gen(function* () {
|
|
1598
|
+
for (const slot of [...slots].reverse()) {
|
|
1599
|
+
if (slot.lifecycle === "stopped" || slot.lifecycle === "disabled") continue;
|
|
1600
|
+
slot.lifecycle = transitionDeploymentComponentLifecycle(slot.lifecycle, "stopping");
|
|
1601
|
+
if (!slot.adapter) {
|
|
1602
|
+
slot.lifecycle = transitionDeploymentComponentLifecycle(slot.lifecycle, slot.definition.enabled && slot.agentEnabled ? "stopped" : "disabled");
|
|
1603
|
+
delete slot.error;
|
|
1604
|
+
continue;
|
|
1605
|
+
}
|
|
1606
|
+
const stopped = yield* Effect.gen(function* () {
|
|
1607
|
+
yield* slot.adapter.stop();
|
|
1608
|
+
if (yield* Effect.try({
|
|
1609
|
+
try: () => slot.adapter.running(),
|
|
1610
|
+
catch: toDeploymentFailure
|
|
1611
|
+
})) return yield* Effect.fail(/* @__PURE__ */ new Error("component stop completed but the adapter is still running"));
|
|
1612
|
+
}).pipe(Effect.exit);
|
|
1613
|
+
if (Exit.isFailure(stopped)) {
|
|
1614
|
+
const error = Cause.squash(stopped.cause);
|
|
1615
|
+
errors.push(error);
|
|
1616
|
+
slot.error = formatDeploymentFailure(error);
|
|
1617
|
+
slot.lifecycle = transitionDeploymentComponentLifecycle(slot.lifecycle, "cleanup-required");
|
|
1618
|
+
continue;
|
|
1619
|
+
}
|
|
1620
|
+
slot.lifecycle = transitionDeploymentComponentLifecycle(slot.lifecycle, slot.definition.enabled && slot.agentEnabled ? "stopped" : "disabled");
|
|
1621
|
+
delete slot.error;
|
|
1622
|
+
}
|
|
1623
|
+
});
|
|
1282
1624
|
}
|
|
1283
|
-
function
|
|
1284
|
-
|
|
1625
|
+
function componentStatus(slot) {
|
|
1626
|
+
const observed = observeRunning(slot);
|
|
1627
|
+
return Object.freeze({
|
|
1628
|
+
enabled: slot.definition.enabled,
|
|
1629
|
+
...slot.error ? { error: slot.error } : {},
|
|
1630
|
+
lifecycle: slot.lifecycle,
|
|
1631
|
+
required: slot.definition.required,
|
|
1632
|
+
running: slot.lifecycle === "running" && observed.running
|
|
1633
|
+
});
|
|
1285
1634
|
}
|
|
1286
|
-
function
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1635
|
+
function backgroundStatus(slot) {
|
|
1636
|
+
let supervisor;
|
|
1637
|
+
try {
|
|
1638
|
+
supervisor = slot.adapter?.status?.();
|
|
1639
|
+
} catch (error) {
|
|
1640
|
+
slot.error = formatDeploymentFailure(error);
|
|
1641
|
+
}
|
|
1642
|
+
return Object.freeze({
|
|
1643
|
+
...componentStatus(slot),
|
|
1644
|
+
...supervisor ? { supervisor } : {}
|
|
1645
|
+
});
|
|
1290
1646
|
}
|
|
1291
|
-
function
|
|
1292
|
-
return
|
|
1647
|
+
function isSlotReady(slot) {
|
|
1648
|
+
return isRequiredDeploymentComponentReady({
|
|
1649
|
+
enabled: slot.definition.enabled,
|
|
1650
|
+
lifecycle: slot.lifecycle,
|
|
1651
|
+
required: slot.definition.required,
|
|
1652
|
+
running: observeRunning(slot).running
|
|
1653
|
+
});
|
|
1293
1654
|
}
|
|
1294
|
-
function
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1655
|
+
function observeRunning(slot) {
|
|
1656
|
+
if (!slot.adapter) return { running: false };
|
|
1657
|
+
try {
|
|
1658
|
+
return { running: slot.adapter.running() };
|
|
1659
|
+
} catch (error) {
|
|
1660
|
+
return {
|
|
1661
|
+
error: formatDeploymentFailure(error),
|
|
1662
|
+
running: false
|
|
1663
|
+
};
|
|
1300
1664
|
}
|
|
1301
|
-
|
|
1665
|
+
}
|
|
1666
|
+
function bindResolvedProjectSpaceRevision(definition, projectSpaces) {
|
|
1667
|
+
if (!definition.projectSpaceId) return definition;
|
|
1668
|
+
const projectSpace = projectSpaces.get(definition.projectSpaceId);
|
|
1669
|
+
if (!projectSpace) throw new RivusDeploymentDaemonLifecycleError(`agent ${definition.agentId} references unresolved Project Space: ${definition.projectSpaceId}`);
|
|
1670
|
+
return Object.freeze({
|
|
1671
|
+
...definition,
|
|
1672
|
+
projectSpaceRevision: projectSpace.revision
|
|
1673
|
+
});
|
|
1302
1674
|
}
|
|
1303
1675
|
//#endregion
|
|
1304
|
-
//#region src/application/
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1676
|
+
//#region src/modules/deployment-control/application/resolution/automation-runtime-definition.ts
|
|
1677
|
+
function resolveRivusAutomationRuntimeDefinition(definition, requestedToolIds, requestedSkillIds) {
|
|
1678
|
+
return restrictRivusAgentDefinitionGrants(definition, {
|
|
1679
|
+
memory: {
|
|
1680
|
+
scopes: [],
|
|
1681
|
+
tool: false
|
|
1682
|
+
},
|
|
1683
|
+
runtimeToolIds: [],
|
|
1684
|
+
skillIds: requestedSkillIds,
|
|
1685
|
+
toolIds: requestedToolIds
|
|
1686
|
+
});
|
|
1687
|
+
}
|
|
1688
|
+
//#endregion
|
|
1689
|
+
//#region src/modules/deployment-control/application/resolution/deployment-resolution.ts
|
|
1690
|
+
var RivusPluginLoadError = class extends Error {
|
|
1691
|
+
pluginId;
|
|
1692
|
+
moduleSpecifier;
|
|
1693
|
+
name = "RivusPluginLoadError";
|
|
1694
|
+
constructor(pluginId, moduleSpecifier, message, options) {
|
|
1695
|
+
super(message, options);
|
|
1696
|
+
this.pluginId = pluginId;
|
|
1697
|
+
this.moduleSpecifier = moduleSpecifier;
|
|
1698
|
+
}
|
|
1699
|
+
};
|
|
1700
|
+
function resolveRivusDeployment(input) {
|
|
1701
|
+
return Effect.gen(function* () {
|
|
1702
|
+
yield* Effect.try({
|
|
1703
|
+
try: () => validateRivusDeploymentManifest(input.manifest),
|
|
1704
|
+
catch: toDeploymentFailure
|
|
1312
1705
|
});
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1706
|
+
const catalog = input.agentCatalog.createPluginCatalog();
|
|
1707
|
+
const pluginStatuses = [];
|
|
1708
|
+
const statusByPlugin = /* @__PURE__ */ new Map();
|
|
1709
|
+
for (const declaration of input.manifest.plugins) {
|
|
1710
|
+
const loaded = yield* input.pluginLoader.load({
|
|
1711
|
+
deploymentRoot: input.deploymentRoot,
|
|
1712
|
+
module: declaration.module,
|
|
1713
|
+
pluginId: declaration.id
|
|
1714
|
+
}).pipe(Effect.flatMap((plugin) => Effect.try({
|
|
1715
|
+
try: () => {
|
|
1716
|
+
if (plugin.manifest.id !== declaration.id) throw new Error(`plugin manifest id ${plugin.manifest.id} does not match declaration ${declaration.id}`);
|
|
1717
|
+
catalog.registerPlugin(plugin);
|
|
1718
|
+
return plugin;
|
|
1719
|
+
},
|
|
1720
|
+
catch: toDeploymentFailure
|
|
1721
|
+
})), Effect.either);
|
|
1722
|
+
if (Either.isLeft(loaded)) {
|
|
1723
|
+
const message = formatDeploymentFailure(loaded.left);
|
|
1724
|
+
if (declaration.required) return yield* Effect.fail(new RivusPluginLoadError(declaration.id, declaration.module, `required plugin ${declaration.id} failed to load: ${message}`, { cause: loaded.left }));
|
|
1725
|
+
const status = Object.freeze({
|
|
1726
|
+
error: message,
|
|
1727
|
+
id: declaration.id,
|
|
1728
|
+
module: declaration.module,
|
|
1729
|
+
required: false,
|
|
1730
|
+
status: "failed"
|
|
1731
|
+
});
|
|
1732
|
+
pluginStatuses.push(status);
|
|
1733
|
+
statusByPlugin.set(declaration.id, status);
|
|
1734
|
+
continue;
|
|
1735
|
+
}
|
|
1736
|
+
const status = Object.freeze({
|
|
1737
|
+
id: declaration.id,
|
|
1738
|
+
module: declaration.module,
|
|
1739
|
+
required: declaration.required,
|
|
1740
|
+
status: "loaded",
|
|
1741
|
+
version: loaded.right.manifest.version
|
|
1320
1742
|
});
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1743
|
+
pluginStatuses.push(status);
|
|
1744
|
+
statusByPlugin.set(declaration.id, status);
|
|
1745
|
+
}
|
|
1746
|
+
const agentStatuses = [];
|
|
1747
|
+
const definitions = [];
|
|
1748
|
+
for (const agent of input.manifest.agents) {
|
|
1749
|
+
const pluginStatus = statusByPlugin.get(agent.pluginId);
|
|
1750
|
+
if (pluginStatus.status === "failed") {
|
|
1751
|
+
agentStatuses.push(Object.freeze({
|
|
1752
|
+
agentId: agent.agentId,
|
|
1753
|
+
pluginId: agent.pluginId,
|
|
1754
|
+
profileId: agent.profileId,
|
|
1755
|
+
reason: `plugin ${agent.pluginId} is unavailable: ${pluginStatus.error}`,
|
|
1756
|
+
status: "disabled"
|
|
1757
|
+
}));
|
|
1758
|
+
continue;
|
|
1759
|
+
}
|
|
1760
|
+
const resolved = yield* Effect.try({
|
|
1761
|
+
try: () => {
|
|
1762
|
+
const baseDefinition = input.agentCatalog.resolve(catalog, agent);
|
|
1763
|
+
const projectSpace = agent.projectSpaceId ? input.manifest.projectSpaces?.find(({ id }) => id === agent.projectSpaceId) : void 0;
|
|
1764
|
+
return projectSpace ? deepFreeze({
|
|
1765
|
+
...baseDefinition,
|
|
1766
|
+
projectSpaceRevision: createRivusProjectSpaceDeclarationRevision(projectSpace)
|
|
1767
|
+
}) : baseDefinition;
|
|
1768
|
+
},
|
|
1769
|
+
catch: toDeploymentFailure
|
|
1770
|
+
}).pipe(Effect.either);
|
|
1771
|
+
if (Either.isLeft(resolved)) {
|
|
1772
|
+
const declaration = input.manifest.plugins.find(({ id }) => id === agent.pluginId);
|
|
1773
|
+
if (declaration.required) return yield* Effect.fail(new RivusPluginLoadError(declaration.id, declaration.module, `deployment ${agent.agentId} failed to resolve: ${resolved.left.message}`, { cause: resolved.left }));
|
|
1774
|
+
agentStatuses.push(Object.freeze({
|
|
1775
|
+
agentId: agent.agentId,
|
|
1776
|
+
pluginId: agent.pluginId,
|
|
1777
|
+
profileId: agent.profileId,
|
|
1778
|
+
reason: resolved.left.message,
|
|
1779
|
+
status: "disabled"
|
|
1780
|
+
}));
|
|
1781
|
+
continue;
|
|
1782
|
+
}
|
|
1783
|
+
definitions.push(resolved.right);
|
|
1784
|
+
agentStatuses.push(Object.freeze({
|
|
1785
|
+
agentId: agent.agentId,
|
|
1786
|
+
definition: resolved.right,
|
|
1787
|
+
pluginId: agent.pluginId,
|
|
1788
|
+
profileId: agent.profileId,
|
|
1789
|
+
status: "enabled"
|
|
1790
|
+
}));
|
|
1791
|
+
}
|
|
1792
|
+
const agentStatusById = new Map(agentStatuses.map((agent) => [agent.agentId, agent]));
|
|
1793
|
+
const automationTemplates = new Map(catalog.snapshot().automations.map((template) => [template.id, template]));
|
|
1794
|
+
const automationDefinitions = [];
|
|
1795
|
+
for (const automation of input.manifest.automations ?? []) {
|
|
1796
|
+
const agent = agentStatusById.get(automation.agentId);
|
|
1797
|
+
if (!agent || agent.status === "disabled" || !agent.definition) continue;
|
|
1798
|
+
const deliveryEndpoint = input.manifest.endpoints.find(({ id }) => id === automation.delivery.endpointId);
|
|
1799
|
+
const presentationAgent = agentStatusById.get(deliveryEndpoint.agentId);
|
|
1800
|
+
if (!presentationAgent || presentationAgent.status === "disabled" || !presentationAgent.definition) continue;
|
|
1801
|
+
const template = automationTemplates.get(automation.templateId);
|
|
1802
|
+
if (!template) return yield* Effect.fail(/* @__PURE__ */ new Error(`automation ${automation.id} references unknown template: ${automation.templateId}`));
|
|
1803
|
+
if (template.pluginId !== agent.pluginId || template.profileId !== agent.profileId) return yield* Effect.fail(/* @__PURE__ */ new Error(`automation ${automation.id} template is not owned by agent profile ${agent.profileId}`));
|
|
1804
|
+
const definition = yield* Effect.try({
|
|
1805
|
+
try: () => deepFreeze({
|
|
1806
|
+
...automation,
|
|
1807
|
+
runtimeDefinition: resolveRivusAutomationRuntimeDefinition(agent.definition, template.requestedToolIds, template.requestedSkillIds),
|
|
1808
|
+
template
|
|
1809
|
+
}),
|
|
1810
|
+
catch: toDeploymentFailure
|
|
1811
|
+
});
|
|
1812
|
+
automationDefinitions.push(definition);
|
|
1813
|
+
}
|
|
1814
|
+
return Object.freeze({
|
|
1815
|
+
agents: Object.freeze(agentStatuses),
|
|
1816
|
+
automationDefinitions: Object.freeze(automationDefinitions),
|
|
1817
|
+
catalog,
|
|
1818
|
+
definitions: Object.freeze(definitions),
|
|
1819
|
+
manifest: input.manifest,
|
|
1820
|
+
plugins: Object.freeze(pluginStatuses)
|
|
1821
|
+
});
|
|
1822
|
+
});
|
|
1324
1823
|
}
|
|
1325
1824
|
//#endregion
|
|
1326
|
-
//#region src/
|
|
1327
|
-
|
|
1328
|
-
|
|
1825
|
+
//#region src/adapters/deployment/manifest/node-rivus-deployment-manifest.ts
|
|
1826
|
+
var RivusDeploymentManifestError = class extends Error {
|
|
1827
|
+
manifestPath;
|
|
1828
|
+
name = "RivusDeploymentManifestError";
|
|
1829
|
+
constructor(manifestPath, message, options) {
|
|
1830
|
+
super(message, options);
|
|
1831
|
+
this.manifestPath = manifestPath;
|
|
1832
|
+
}
|
|
1833
|
+
};
|
|
1834
|
+
function loadRivusDeploymentManifest(manifestPath, options = {}) {
|
|
1835
|
+
const maxBytes = options.maxBytes ?? 1024 * 1024;
|
|
1836
|
+
return Effect.tryPromise({
|
|
1837
|
+
try: async () => {
|
|
1838
|
+
const metadata = await stat(manifestPath);
|
|
1839
|
+
if (!metadata.isFile()) throw new Error("deployment manifest must be a regular file");
|
|
1840
|
+
if (metadata.size > maxBytes) throw new Error(`deployment manifest exceeds ${maxBytes} byte limit`);
|
|
1841
|
+
const bytes = await readFile(manifestPath);
|
|
1842
|
+
const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
1843
|
+
return parseRivusDeploymentManifest(JSON.parse(text));
|
|
1844
|
+
},
|
|
1845
|
+
catch: (cause) => cause instanceof RivusDeploymentManifestError ? cause : new RivusDeploymentManifestError(manifestPath, `failed to load Rivus deployment manifest: ${cause instanceof Error ? cause.message : String(cause)}`, { cause })
|
|
1846
|
+
});
|
|
1329
1847
|
}
|
|
1330
1848
|
//#endregion
|
|
1331
|
-
//#region src/
|
|
1849
|
+
//#region src/modules/agent-host/domain/instance/agent-instance.ts
|
|
1332
1850
|
var AgentInstanceConflict = class extends Error {
|
|
1333
1851
|
name = "AgentInstanceConflict";
|
|
1334
1852
|
};
|
|
1335
|
-
function
|
|
1853
|
+
function agentInstanceBindingKey(binding, agentId) {
|
|
1854
|
+
const bindingId = binding.kind === "endpoint" ? binding.endpointId : binding.kind === "automation" ? binding.automationId : binding.agentId;
|
|
1855
|
+
return `${binding.kind}:${bindingId}:${agentId}`;
|
|
1856
|
+
}
|
|
1857
|
+
function createAgentInstanceRecord(input) {
|
|
1858
|
+
return Object.freeze({
|
|
1859
|
+
agentId: input.agentId,
|
|
1860
|
+
binding: Object.freeze({ ...input.binding }),
|
|
1861
|
+
bindingKey: agentInstanceBindingKey(input.binding, input.agentId),
|
|
1862
|
+
instanceId: input.instanceId,
|
|
1863
|
+
runtimeGenerationId: input.runtimeGenerationId
|
|
1864
|
+
});
|
|
1865
|
+
}
|
|
1866
|
+
//#endregion
|
|
1867
|
+
//#region src/modules/agent-host/application/registry/agent-instance-registry.ts
|
|
1868
|
+
function createEffectAgentInstanceRegistry(identity, options = {}) {
|
|
1336
1869
|
const records = /* @__PURE__ */ new Map();
|
|
1337
1870
|
for (const record of options.initialRecords ?? []) {
|
|
1871
|
+
if (agentInstanceBindingKey(record.binding, record.agentId) !== record.bindingKey) throw new AgentInstanceConflict(`invalid binding key: ${record.bindingKey}`);
|
|
1338
1872
|
if (records.has(record.bindingKey)) throw new AgentInstanceConflict(`duplicate binding: ${record.bindingKey}`);
|
|
1339
|
-
records.set(record.bindingKey, Object.freeze({
|
|
1873
|
+
records.set(record.bindingKey, Object.freeze({
|
|
1874
|
+
...record,
|
|
1875
|
+
binding: Object.freeze({ ...record.binding })
|
|
1876
|
+
}));
|
|
1340
1877
|
}
|
|
1341
|
-
const resolveBinding = (binding, definition) => {
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
if (existing
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1878
|
+
const resolveBinding = (binding, definition) => Effect.try({
|
|
1879
|
+
try: () => {
|
|
1880
|
+
const runtimeGenerationId = identity.createRuntimeGenerationId({
|
|
1881
|
+
agentId: definition.agentId,
|
|
1882
|
+
profileRevision: definition.profileRevision,
|
|
1883
|
+
projectSpaceId: definition.projectSpaceId ?? null,
|
|
1884
|
+
projectSpaceRevision: definition.projectSpaceRevision ?? null,
|
|
1885
|
+
runtimeToolGrantRevision: definition.runtimeToolGrantSet.revision,
|
|
1886
|
+
skillGrantRevision: definition.skillGrantSet.revision,
|
|
1887
|
+
toolGrantRevision: definition.toolGrantSet.revision
|
|
1888
|
+
});
|
|
1889
|
+
const bindingKey = agentInstanceBindingKey(binding, definition.agentId);
|
|
1890
|
+
const existing = records.get(bindingKey);
|
|
1891
|
+
if (existing) {
|
|
1892
|
+
if (existing.runtimeGenerationId !== runtimeGenerationId) throw new AgentInstanceConflict(`binding ${bindingKey} belongs to a different runtime generation`);
|
|
1893
|
+
return existing;
|
|
1894
|
+
}
|
|
1895
|
+
const record = createAgentInstanceRecord({
|
|
1896
|
+
agentId: definition.agentId,
|
|
1897
|
+
binding,
|
|
1898
|
+
instanceId: identity.createInstanceId({
|
|
1899
|
+
bindingKey,
|
|
1900
|
+
runtimeGenerationId
|
|
1901
|
+
}),
|
|
1363
1902
|
runtimeGenerationId
|
|
1364
|
-
})
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
};
|
|
1903
|
+
});
|
|
1904
|
+
records.set(bindingKey, record);
|
|
1905
|
+
return record;
|
|
1906
|
+
},
|
|
1907
|
+
catch: (error) => error instanceof AgentInstanceConflict ? error : new AgentInstanceConflict(String(error))
|
|
1908
|
+
});
|
|
1370
1909
|
return {
|
|
1371
1910
|
resolveAutomation: (automationId, definition) => resolveBinding({
|
|
1372
1911
|
automationId,
|
|
@@ -1380,425 +1919,529 @@ function createAgentInstanceRegistry(options = {}) {
|
|
|
1380
1919
|
endpointId,
|
|
1381
1920
|
kind: "endpoint"
|
|
1382
1921
|
}, definition),
|
|
1383
|
-
snapshot: () => Object.freeze([...records.values()])
|
|
1922
|
+
snapshot: () => Effect.sync(() => Object.freeze([...records.values()]))
|
|
1384
1923
|
};
|
|
1385
1924
|
}
|
|
1386
1925
|
//#endregion
|
|
1387
|
-
//#region src/
|
|
1388
|
-
var
|
|
1389
|
-
name = "
|
|
1926
|
+
//#region src/modules/agent-host/application/routing/agent-host.ts
|
|
1927
|
+
var InvalidAgentHostBinding = class extends Error {
|
|
1928
|
+
name = "InvalidAgentHostBinding";
|
|
1390
1929
|
};
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
const
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
const
|
|
1417
|
-
return
|
|
1418
|
-
}
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
try {
|
|
1436
|
-
return await runtime.run(input);
|
|
1437
|
-
} finally {
|
|
1438
|
-
if (active.get(instance.instanceId) === activeToken) active.delete(instance.instanceId);
|
|
1439
|
-
}
|
|
1440
|
-
}
|
|
1441
|
-
};
|
|
1442
|
-
}
|
|
1443
|
-
async function withTimeout(promise, timeoutMs, message) {
|
|
1444
|
-
let timeout;
|
|
1445
|
-
try {
|
|
1446
|
-
return await Promise.race([promise, new Promise((_resolve, reject) => {
|
|
1447
|
-
timeout = setTimeout(() => reject(/* @__PURE__ */ new Error(`${message} after ${timeoutMs}ms`)), timeoutMs);
|
|
1448
|
-
})]);
|
|
1449
|
-
} finally {
|
|
1450
|
-
if (timeout) clearTimeout(timeout);
|
|
1451
|
-
}
|
|
1930
|
+
function createEffectAgentHost(options) {
|
|
1931
|
+
return Effect.gen(function* () {
|
|
1932
|
+
const definitions = new Map(options.definitions.map((definition) => [definition.agentId, definition]));
|
|
1933
|
+
const endpoints = /* @__PURE__ */ new Map();
|
|
1934
|
+
const automations = /* @__PURE__ */ new Map();
|
|
1935
|
+
const backgroundSessions = /* @__PURE__ */ new Map();
|
|
1936
|
+
for (const endpoint of options.endpoints) {
|
|
1937
|
+
if (endpoints.has(endpoint.id)) return yield* Effect.fail(new InvalidAgentHostBinding(`duplicate endpoint: ${endpoint.id}`));
|
|
1938
|
+
const definition = definitions.get(endpoint.agentId);
|
|
1939
|
+
if (!definition) return yield* Effect.fail(new InvalidAgentHostBinding(`unknown endpoint agent: ${endpoint.agentId}`));
|
|
1940
|
+
if (!definition.endpointIds.includes(endpoint.id)) return yield* Effect.fail(new InvalidAgentHostBinding(`endpoint ${endpoint.id} is not declared by ${endpoint.agentId}`));
|
|
1941
|
+
endpoints.set(endpoint.id, yield* options.registry.resolveEndpoint(endpoint.id, definition));
|
|
1942
|
+
}
|
|
1943
|
+
for (const automation of options.automations ?? []) {
|
|
1944
|
+
if (automations.has(automation.id)) return yield* Effect.fail(new InvalidAgentHostBinding(`duplicate automation: ${automation.id}`));
|
|
1945
|
+
if (!definitions.has(automation.definition.agentId)) return yield* Effect.fail(new InvalidAgentHostBinding(`unknown automation agent: ${automation.definition.agentId}`));
|
|
1946
|
+
automations.set(automation.id, yield* options.registry.resolveAutomation(automation.id, automation.definition));
|
|
1947
|
+
}
|
|
1948
|
+
for (const backgroundSession of options.backgroundSessions ?? []) {
|
|
1949
|
+
if (backgroundSession.definition.agentId !== backgroundSession.agentId) return yield* Effect.fail(new InvalidAgentHostBinding(`background session ${backgroundSession.agentId} cannot bind definition ${backgroundSession.definition.agentId}`));
|
|
1950
|
+
if (backgroundSessions.has(backgroundSession.agentId)) return yield* Effect.fail(new InvalidAgentHostBinding(`duplicate background session agent: ${backgroundSession.agentId}`));
|
|
1951
|
+
if (!definitions.has(backgroundSession.agentId)) return yield* Effect.fail(new InvalidAgentHostBinding(`unknown background session agent: ${backgroundSession.agentId}`));
|
|
1952
|
+
backgroundSessions.set(backgroundSession.agentId, yield* options.registry.resolveBackgroundSession(backgroundSession.agentId, backgroundSession.definition));
|
|
1953
|
+
}
|
|
1954
|
+
const resolve = (records, id, message) => {
|
|
1955
|
+
const instance = records.get(id);
|
|
1956
|
+
return instance ? Effect.succeed(instance) : Effect.fail(new InvalidAgentHostBinding(message));
|
|
1957
|
+
};
|
|
1958
|
+
const resolveEndpoint = (endpointId) => resolve(endpoints, endpointId, `unknown endpoint: ${endpointId}`);
|
|
1959
|
+
const resolveAutomation = (automationId) => resolve(automations, automationId, `unknown automation: ${automationId}`);
|
|
1960
|
+
const resolveBackgroundSession = (agentId) => resolve(backgroundSessions, agentId, `unknown background session agent: ${agentId}`);
|
|
1961
|
+
return {
|
|
1962
|
+
cancelBackgroundSession: (agentId, input) => resolveBackgroundSession(agentId).pipe(Effect.flatMap((instance) => options.runtime.cancel(instance, input))),
|
|
1963
|
+
cancelEndpoint: (endpointId, input) => resolveEndpoint(endpointId).pipe(Effect.flatMap((instance) => options.runtime.cancel(instance, input))),
|
|
1964
|
+
dispose: () => options.runtime.disposeAll(),
|
|
1965
|
+
handleAutomation: (automationId, input) => resolveAutomation(automationId).pipe(Effect.flatMap((instance) => options.runtime.run(instance, input))),
|
|
1966
|
+
handleBackgroundSession: (agentId, input) => resolveBackgroundSession(agentId).pipe(Effect.flatMap((instance) => options.runtime.run(instance, input))),
|
|
1967
|
+
handleEndpoint: (endpointId, input) => resolveEndpoint(endpointId).pipe(Effect.flatMap((instance) => options.runtime.run(instance, input))),
|
|
1968
|
+
resolveAutomation,
|
|
1969
|
+
resolveBackgroundSession,
|
|
1970
|
+
resolveEndpoint,
|
|
1971
|
+
steerEndpoint: (endpointId, input) => resolveEndpoint(endpointId).pipe(Effect.flatMap((instance) => options.runtime.steer(instance, input)))
|
|
1972
|
+
};
|
|
1973
|
+
});
|
|
1452
1974
|
}
|
|
1453
1975
|
//#endregion
|
|
1454
|
-
//#region src/
|
|
1455
|
-
var
|
|
1456
|
-
name = "
|
|
1976
|
+
//#region src/platform/runtime/runtime-pool.ts
|
|
1977
|
+
var RuntimeResourceBusy = class extends Error {
|
|
1978
|
+
name = "RuntimeResourceBusy";
|
|
1457
1979
|
};
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
if (
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
}
|
|
1475
|
-
for (const backgroundSession of options.backgroundSessions ?? []) {
|
|
1476
|
-
if (backgroundSessions.has(backgroundSession.agentId)) throw new InvalidRivusEndpointBinding(`duplicate background session agent: ${backgroundSession.agentId}`);
|
|
1477
|
-
if (!definitions.has(backgroundSession.agentId)) throw new InvalidRivusEndpointBinding(`unknown background session agent: ${backgroundSession.agentId}`);
|
|
1478
|
-
backgroundSessions.set(backgroundSession.agentId, options.runtimePool.registry.resolveBackgroundSession(backgroundSession.agentId, backgroundSession.definition));
|
|
1479
|
-
}
|
|
1480
|
-
const resolveEndpoint = (endpointId) => {
|
|
1481
|
-
const instance = endpoints.get(endpointId);
|
|
1482
|
-
if (!instance) throw new InvalidRivusEndpointBinding(`unknown endpoint: ${endpointId}`);
|
|
1483
|
-
return instance;
|
|
1484
|
-
};
|
|
1485
|
-
const resolveAutomation = (automationId) => {
|
|
1486
|
-
const instance = automations.get(automationId);
|
|
1487
|
-
if (!instance) throw new InvalidRivusEndpointBinding(`unknown automation: ${automationId}`);
|
|
1488
|
-
return instance;
|
|
1489
|
-
};
|
|
1490
|
-
const resolveBackgroundSession = (agentId) => {
|
|
1491
|
-
const instance = backgroundSessions.get(agentId);
|
|
1492
|
-
if (!instance) throw new InvalidRivusEndpointBinding(`unknown background session agent: ${agentId}`);
|
|
1493
|
-
return instance;
|
|
1494
|
-
};
|
|
1980
|
+
var RuntimeResourceDisposed = class extends Error {
|
|
1981
|
+
name = "RuntimeResourceDisposed";
|
|
1982
|
+
};
|
|
1983
|
+
var RuntimePoolDisposalTimedOut = class extends Error {
|
|
1984
|
+
name = "RuntimePoolDisposalTimedOut";
|
|
1985
|
+
};
|
|
1986
|
+
const DEFAULT_RUNTIME_DISPOSE_TIMEOUT_MS = 1e4;
|
|
1987
|
+
function createEffectRuntimePool(options) {
|
|
1988
|
+
const runtimes = createRuntimeCache();
|
|
1989
|
+
const active = /* @__PURE__ */ new Map();
|
|
1990
|
+
const lifecycle = Effect.unsafeMakeSemaphore(1);
|
|
1991
|
+
const existingRuntime = (instance) => Effect.gen(function* () {
|
|
1992
|
+
const selected = yield* runtimes.getExisting(instance.instanceId);
|
|
1993
|
+
if (!selected || !(yield* runtimes.isCurrent(instance.instanceId, selected.entry))) return void 0;
|
|
1994
|
+
return selected.runtime;
|
|
1995
|
+
});
|
|
1495
1996
|
return {
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1997
|
+
cancel: (instance, input) => invokeRuntimeControl(existingRuntime(instance), (runtime) => runtime.cancel?.(input)),
|
|
1998
|
+
disposeAll: () => Effect.gen(function* () {
|
|
1999
|
+
const entries = yield* runtimes.drain();
|
|
2000
|
+
yield* lifecycle.withPermits(1)(Effect.sync(() => active.clear()));
|
|
2001
|
+
yield* disposeRuntimeCacheEntries({
|
|
2002
|
+
dispose: (runtime) => runtime.dispose?.() ?? Effect.void,
|
|
2003
|
+
entries,
|
|
2004
|
+
failureMessage: "runtime pool disposal failed",
|
|
2005
|
+
timeout: {
|
|
2006
|
+
milliseconds: options.disposeTimeoutMs ?? DEFAULT_RUNTIME_DISPOSE_TIMEOUT_MS,
|
|
2007
|
+
onTimeout: () => new RuntimePoolDisposalTimedOut(`runtime pool disposal timed out after ${options.disposeTimeoutMs ?? DEFAULT_RUNTIME_DISPOSE_TIMEOUT_MS}ms`)
|
|
2008
|
+
}
|
|
2009
|
+
});
|
|
2010
|
+
}),
|
|
2011
|
+
run: (instance, input) => Effect.gen(function* () {
|
|
2012
|
+
const selected = yield* runtimes.getOrCreate(instance.instanceId, () => options.createRuntime(instance));
|
|
2013
|
+
if (!(yield* runtimes.isCurrent(instance.instanceId, selected.entry))) return yield* Effect.fail(new RuntimeResourceDisposed(`runtime was disposed before run start: ${instance.instanceId}`));
|
|
2014
|
+
if (selected.runtime.concurrency === "managed") return yield* selected.runtime.run(input);
|
|
2015
|
+
const token = yield* lifecycle.withPermits(1)(Effect.gen(function* () {
|
|
2016
|
+
if (!(yield* runtimes.isCurrent(instance.instanceId, selected.entry))) return yield* Effect.fail(new RuntimeResourceDisposed(`runtime was disposed before run start: ${instance.instanceId}`));
|
|
2017
|
+
if (active.has(instance.instanceId)) return yield* Effect.fail(new RuntimeResourceBusy(`runtime already has an active run: ${instance.instanceId}`));
|
|
2018
|
+
const activeToken = Symbol(instance.instanceId);
|
|
2019
|
+
active.set(instance.instanceId, activeToken);
|
|
2020
|
+
return activeToken;
|
|
2021
|
+
}));
|
|
2022
|
+
return yield* selected.runtime.run(input).pipe(Effect.ensuring(lifecycle.withPermits(1)(Effect.sync(() => {
|
|
2023
|
+
if (active.get(instance.instanceId) === token) active.delete(instance.instanceId);
|
|
2024
|
+
}))));
|
|
2025
|
+
}),
|
|
2026
|
+
steer: (instance, input) => invokeRuntimeControl(existingRuntime(instance), (runtime) => runtime.steer?.(input))
|
|
1505
2027
|
};
|
|
1506
2028
|
}
|
|
1507
2029
|
//#endregion
|
|
1508
|
-
//#region src/application/
|
|
1509
|
-
var
|
|
1510
|
-
name = "
|
|
1511
|
-
};
|
|
1512
|
-
var RivusDeploymentReadinessError = class extends Error {
|
|
1513
|
-
endpointIds;
|
|
1514
|
-
name = "RivusDeploymentReadinessError";
|
|
1515
|
-
constructor(endpointIds) {
|
|
1516
|
-
super(`required endpoint startup failed: ${endpointIds.join(", ")}`);
|
|
1517
|
-
this.endpointIds = endpointIds;
|
|
1518
|
-
}
|
|
2030
|
+
//#region src/modules/agent-host/application/runtime/agent-host-runtime.ts
|
|
2031
|
+
var AgentInstanceBusy = class extends Error {
|
|
2032
|
+
name = "AgentInstanceBusy";
|
|
1519
2033
|
};
|
|
1520
|
-
var
|
|
1521
|
-
|
|
1522
|
-
name = "RivusDeploymentAutomationReadinessError";
|
|
1523
|
-
constructor(automationIds) {
|
|
1524
|
-
super(`required automation startup failed: ${automationIds.join(", ")}`);
|
|
1525
|
-
this.automationIds = automationIds;
|
|
1526
|
-
}
|
|
2034
|
+
var AgentRuntimeDisposed = class extends Error {
|
|
2035
|
+
name = "AgentRuntimeDisposed";
|
|
1527
2036
|
};
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
const
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
deploymentRoot: options.deploymentRoot
|
|
1535
|
-
});
|
|
1536
|
-
return [projectSpace.id, projectSpace];
|
|
1537
|
-
})));
|
|
1538
|
-
const definitions = new Map(deployment.definitions.map((definition) => [definition.agentId, definition]));
|
|
1539
|
-
const automationDefinitions = new Map(deployment.automationDefinitions.map((definition) => [definition.id, definition]));
|
|
1540
|
-
const backgroundSessionsConfig = deployment.manifest.backgroundSessions;
|
|
1541
|
-
const backgroundDefinitions = /* @__PURE__ */ new Map();
|
|
1542
|
-
if (backgroundSessionsConfig?.enabled) for (const agent of deployment.agents) {
|
|
1543
|
-
if (agent.status !== "enabled" || !agent.definition) continue;
|
|
1544
|
-
backgroundDefinitions.set(agent.agentId, narrowBackgroundSessionDefinition(agent.definition));
|
|
1545
|
-
}
|
|
1546
|
-
const runtimePool = createAgentRuntimePool({
|
|
1547
|
-
createRuntime: (instance) => {
|
|
1548
|
-
const definition = instance.binding.kind === "automation" ? automationDefinitions.get(instance.binding.automationId)?.runtimeDefinition : instance.binding.kind === "background-session" ? backgroundDefinitions.get(instance.agentId) : definitions.get(instance.agentId);
|
|
1549
|
-
if (!definition) throw new RivusDeploymentDaemonLifecycleError(`runtime instance references unknown agent: ${instance.agentId}`);
|
|
1550
|
-
const projectSpace = definition.projectSpaceId ? projectSpaces.get(definition.projectSpaceId) : void 0;
|
|
1551
|
-
return options.createRuntime({
|
|
1552
|
-
...instance,
|
|
1553
|
-
catalog: deployment.catalog,
|
|
1554
|
-
definition,
|
|
1555
|
-
...projectSpace ? { projectSpace } : {}
|
|
1556
|
-
});
|
|
1557
|
-
},
|
|
1558
|
-
registry: createAgentInstanceRegistry(options.initialInstanceRecords ? { initialRecords: options.initialInstanceRecords } : {})
|
|
2037
|
+
//#endregion
|
|
2038
|
+
//#region src/modules/agent-host/infrastructure/runtime/effect-agent-runtime-pool.ts
|
|
2039
|
+
function createAgentHostRuntimePool(factory, disposeTimeoutMs) {
|
|
2040
|
+
const pool = createEffectRuntimePool({
|
|
2041
|
+
createRuntime: (instance) => factory.create(instance),
|
|
2042
|
+
...disposeTimeoutMs === void 0 ? {} : { disposeTimeoutMs }
|
|
1559
2043
|
});
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
2044
|
+
return {
|
|
2045
|
+
cancel: (instance, input) => pool.cancel(instance, input),
|
|
2046
|
+
disposeAll: () => pool.disposeAll().pipe(Effect.mapError(mapRuntimePoolError)),
|
|
2047
|
+
run: (instance, input) => pool.run(instance, input).pipe(Effect.mapError(mapRuntimePoolError)),
|
|
2048
|
+
steer: (instance, input) => pool.steer(instance, input)
|
|
2049
|
+
};
|
|
2050
|
+
}
|
|
2051
|
+
function mapRuntimePoolError(error) {
|
|
2052
|
+
if (error instanceof RuntimeResourceBusy) return new AgentInstanceBusy(error.message.replace(/^runtime /, "agent instance "));
|
|
2053
|
+
if (error instanceof RuntimeResourceDisposed) return new AgentRuntimeDisposed(error.message.replace(/^runtime /, "agent runtime "));
|
|
2054
|
+
if (error instanceof RuntimePoolDisposalTimedOut) return new AgentRuntimeDisposed(error.message.replace(/^runtime pool /, "agent runtime "));
|
|
2055
|
+
return error;
|
|
2056
|
+
}
|
|
2057
|
+
//#endregion
|
|
2058
|
+
//#region src/modules/agent-host/module.ts
|
|
2059
|
+
function createAgentInstanceRegistryModule(options = {}) {
|
|
2060
|
+
return createEffectAgentInstanceRegistry({
|
|
2061
|
+
createInstanceId: (input) => createStableId("instance", input),
|
|
2062
|
+
createRuntimeGenerationId: (input) => createStableId("generation", input)
|
|
2063
|
+
}, options);
|
|
2064
|
+
}
|
|
2065
|
+
function createAgentHostFromRuntimePort(options) {
|
|
2066
|
+
return createEffectAgentHost(options);
|
|
2067
|
+
}
|
|
2068
|
+
function createAgentHostRuntimePort(factory, disposeTimeoutMs) {
|
|
2069
|
+
return createAgentHostRuntimePool(factory, disposeTimeoutMs);
|
|
2070
|
+
}
|
|
2071
|
+
function createAgentHostModule(options) {
|
|
2072
|
+
const registry = createAgentInstanceRegistryModule(options.initialInstanceRecords ? { initialRecords: options.initialInstanceRecords } : {});
|
|
2073
|
+
const runtime = createAgentHostRuntimePort(options.runtimeFactory, options.disposeTimeoutMs);
|
|
2074
|
+
return createEffectAgentHost({
|
|
2075
|
+
...options.automations ? { automations: options.automations } : {},
|
|
2076
|
+
...options.backgroundSessions ? { backgroundSessions: options.backgroundSessions } : {},
|
|
2077
|
+
definitions: options.definitions,
|
|
2078
|
+
endpoints: options.endpoints,
|
|
2079
|
+
registry,
|
|
2080
|
+
runtime
|
|
1568
2081
|
});
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
2082
|
+
}
|
|
2083
|
+
//#endregion
|
|
2084
|
+
//#region src/adapters/deployment/bootstrap/process-deployment-adapters.ts
|
|
2085
|
+
function createProcessDeploymentControlPorts(factories, backgroundSessions, runEffect) {
|
|
2086
|
+
return {
|
|
2087
|
+
agentCatalog: createRivusAgentCatalog({ toolDescriptorProviders: [createRivusHostToolDescriptorProvider({ backgroundSessions })] }),
|
|
2088
|
+
agentHostFactory: { create: (input) => createAgentHostModule({
|
|
2089
|
+
automations: input.automations,
|
|
2090
|
+
backgroundSessions: input.backgroundSessions,
|
|
2091
|
+
definitions: input.definitions,
|
|
2092
|
+
endpoints: input.endpoints,
|
|
2093
|
+
...input.initialInstanceRecords ? { initialInstanceRecords: input.initialInstanceRecords } : {},
|
|
2094
|
+
runtimeFactory: input.runtimeFactory
|
|
2095
|
+
}) },
|
|
2096
|
+
...factories.createAutomation ? { automationFactory: { create: (input) => adaptAutomationFactory(factories.createAutomation, input, runEffect) } } : {},
|
|
2097
|
+
...factories.createBackgroundSession ? { backgroundSessionFactory: { create: (input) => adaptBackgroundSessionFactory(factories.createBackgroundSession, input, runEffect) } } : {},
|
|
2098
|
+
endpointFactory: { create: (input) => adaptEndpointFactory(factories.createEndpoint, input, runEffect) },
|
|
2099
|
+
runtimeFactory: { create: (input) => Effect.tryPromise({
|
|
2100
|
+
try: () => Promise.resolve(factories.createRuntime(input)),
|
|
2101
|
+
catch: toError$3
|
|
2102
|
+
}).pipe(Effect.map((runtime) => toDeploymentRuntime(runtime, runEffect))) }
|
|
2103
|
+
};
|
|
2104
|
+
}
|
|
2105
|
+
function adaptEndpointFactory(create, input, runEffect) {
|
|
2106
|
+
return Effect.tryPromise({
|
|
2107
|
+
try: async () => {
|
|
2108
|
+
return toLifecycleAdapter(await create({
|
|
2109
|
+
agentId: input.agentId,
|
|
2110
|
+
cancel: (request) => runEffect(input.cancel(request)),
|
|
2111
|
+
definition: input.definition,
|
|
2112
|
+
endpointId: input.endpointId,
|
|
2113
|
+
handle: (request) => runEffect(input.handle(toEffectProcessAgentRuntimeInput(request))),
|
|
2114
|
+
instanceId: input.instanceId,
|
|
2115
|
+
...input.projectSpaceId ? { projectSpaceId: input.projectSpaceId } : {},
|
|
2116
|
+
steer: (request) => runEffect(input.steer(request))
|
|
2117
|
+
}));
|
|
2118
|
+
},
|
|
2119
|
+
catch: toError$3
|
|
1578
2120
|
});
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
})),
|
|
1589
|
-
...backgroundSessionSlot ? { backgroundSessions: [...backgroundDefinitions].map(([agentId, definition]) => ({
|
|
1590
|
-
agentId,
|
|
1591
|
-
definition
|
|
1592
|
-
})) } : {},
|
|
1593
|
-
definitions: deployment.definitions,
|
|
1594
|
-
endpoints: slots.filter((slot) => slot.definition.enabled && slot.agentEnabled).map((slot) => ({
|
|
1595
|
-
agentId: slot.definition.agentId,
|
|
1596
|
-
id: slot.definition.id
|
|
2121
|
+
}
|
|
2122
|
+
function adaptAutomationFactory(create, input, runEffect) {
|
|
2123
|
+
return Effect.tryPromise({
|
|
2124
|
+
try: async () => toLifecycleAdapter(await create({
|
|
2125
|
+
automationId: input.automationId,
|
|
2126
|
+
definition: input.definition,
|
|
2127
|
+
deliveryEndpoint: input.deliveryEndpoint,
|
|
2128
|
+
instanceId: input.instanceId,
|
|
2129
|
+
run: (request) => runEffect(input.run(request))
|
|
1597
2130
|
})),
|
|
1598
|
-
|
|
1599
|
-
});
|
|
1600
|
-
const slotById = new Map(slots.map((slot) => [slot.definition.id, slot]));
|
|
1601
|
-
let lifecycle = "stopped";
|
|
1602
|
-
const canRunIntake = () => lifecycle === "running" || lifecycle === "degraded";
|
|
1603
|
-
const canRunEndpointIntake = (slot) => (canRunIntake() || lifecycle === "starting") && (slot.lifecycle === "running" || slot.lifecycle === "starting") && (slot.adapter?.running() ?? false);
|
|
1604
|
-
const handleEndpoint = async (endpointId, input) => {
|
|
1605
|
-
const slot = slotById.get(endpointId);
|
|
1606
|
-
if (!slot) throw new RivusDeploymentDaemonLifecycleError(`unknown endpoint: ${endpointId}`);
|
|
1607
|
-
if (!canRunEndpointIntake(slot)) throw new RivusDeploymentDaemonLifecycleError(`endpoint ${endpointId} cannot accept intake while ${slot.lifecycle}`);
|
|
1608
|
-
return host.handleEndpoint(endpointId, input);
|
|
1609
|
-
};
|
|
1610
|
-
const endpointStatus = (slot) => Object.freeze({
|
|
1611
|
-
...componentStatus(slot),
|
|
1612
|
-
agentId: slot.definition.agentId,
|
|
1613
|
-
endpointId: slot.definition.id
|
|
1614
|
-
});
|
|
1615
|
-
const automationStatus = (slot) => Object.freeze({
|
|
1616
|
-
...componentStatus(slot),
|
|
1617
|
-
agentId: slot.definition.agentId,
|
|
1618
|
-
automationId: slot.definition.id
|
|
1619
|
-
});
|
|
1620
|
-
const backgroundSessionStatus = (slot) => Object.freeze({
|
|
1621
|
-
...componentStatus(slot),
|
|
1622
|
-
...slot.adapter?.status ? { supervisor: slot.adapter.status() } : {}
|
|
2131
|
+
catch: toError$3
|
|
1623
2132
|
});
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
2133
|
+
}
|
|
2134
|
+
function adaptBackgroundSessionFactory(create, input, runEffect) {
|
|
2135
|
+
return Effect.tryPromise({
|
|
2136
|
+
try: async () => {
|
|
2137
|
+
const adapter = await create({
|
|
2138
|
+
agentIds: input.agentIds,
|
|
2139
|
+
cancel: (request) => runEffect(input.cancel(request)),
|
|
2140
|
+
config: input.config,
|
|
2141
|
+
run: (request) => runEffect(input.run({
|
|
2142
|
+
agentId: request.agentId,
|
|
2143
|
+
invocation: request.invocation,
|
|
2144
|
+
...request.onUpdate ? { onUpdate: (update) => Effect.tryPromise({
|
|
2145
|
+
try: async () => request.onUpdate(update),
|
|
2146
|
+
catch: toError$3
|
|
2147
|
+
}) } : {},
|
|
2148
|
+
sessionKey: request.sessionKey,
|
|
2149
|
+
text: request.text
|
|
2150
|
+
}))
|
|
2151
|
+
});
|
|
2152
|
+
return {
|
|
2153
|
+
...toLifecycleAdapter(adapter),
|
|
2154
|
+
...adapter.status ? { status: () => adapter.status() } : {}
|
|
2155
|
+
};
|
|
2156
|
+
},
|
|
2157
|
+
catch: toError$3
|
|
1649
2158
|
});
|
|
2159
|
+
}
|
|
2160
|
+
function toDeploymentRuntime(runtime, runEffect) {
|
|
2161
|
+
const adapted = toEffectAgentRuntime(runtime, runEffect);
|
|
2162
|
+
const cancel = adapted.cancel?.bind(adapted);
|
|
2163
|
+
const dispose = adapted.dispose?.bind(adapted);
|
|
2164
|
+
const steer = adapted.steer?.bind(adapted);
|
|
1650
2165
|
return {
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
start: async () => {
|
|
1657
|
-
if (lifecycle === "running") return;
|
|
1658
|
-
if (lifecycle === "degraded") {
|
|
1659
|
-
assertRequiredReadiness();
|
|
1660
|
-
return;
|
|
1661
|
-
}
|
|
1662
|
-
if (lifecycle !== "stopped") throw new RivusDeploymentDaemonLifecycleError(`cannot start deployment daemon while ${lifecycle}`);
|
|
1663
|
-
lifecycle = "starting";
|
|
1664
|
-
let degraded = false;
|
|
1665
|
-
for (const slot of slots) {
|
|
1666
|
-
const slotDegraded = await startSlot(slot, slot.agentEnabled, () => options.createEndpoint({
|
|
1667
|
-
agentId: slot.definition.agentId,
|
|
1668
|
-
cancel: (input) => host.cancelEndpoint(slot.definition.id, input),
|
|
1669
|
-
definition: slot.definition,
|
|
1670
|
-
endpointId: slot.definition.id,
|
|
1671
|
-
handle: (input) => handleEndpoint(slot.definition.id, input),
|
|
1672
|
-
instanceId: host.resolveEndpoint(slot.definition.id).instanceId,
|
|
1673
|
-
steer: (input) => host.steerEndpoint(slot.definition.id, input),
|
|
1674
|
-
...definitions.get(slot.definition.agentId)?.projectSpaceId ? { projectSpaceId: definitions.get(slot.definition.agentId).projectSpaceId } : {}
|
|
1675
|
-
}));
|
|
1676
|
-
degraded ||= slotDegraded;
|
|
1677
|
-
}
|
|
1678
|
-
for (const slot of automationSlots) {
|
|
1679
|
-
const resolvedDefinition = slot.resolvedDefinition;
|
|
1680
|
-
const deliverySlot = slotById.get(slot.definition.delivery.endpointId);
|
|
1681
|
-
const presentationReady = deliverySlot.lifecycle === "running" && (deliverySlot.adapter?.running() ?? false);
|
|
1682
|
-
const slotDegraded = await startSlot(slot, slot.agentEnabled && resolvedDefinition !== void 0 && presentationReady, async () => {
|
|
1683
|
-
if (!options.createAutomation) throw new RivusDeploymentDaemonLifecycleError("deployment bootstrap does not provide Automation adapters");
|
|
1684
|
-
if (!resolvedDefinition) throw new RivusDeploymentDaemonLifecycleError(`automation definition not resolved: ${slot.definition.id}`);
|
|
1685
|
-
return options.createAutomation({
|
|
1686
|
-
automationId: slot.definition.id,
|
|
1687
|
-
definition: resolvedDefinition,
|
|
1688
|
-
deliveryEndpoint: deliverySlot.definition,
|
|
1689
|
-
instanceId: host.resolveAutomation(slot.definition.id).instanceId,
|
|
1690
|
-
run: (input) => host.handleAutomation(slot.definition.id, {
|
|
1691
|
-
invocation: {
|
|
1692
|
-
allowedActorOpenIds: [],
|
|
1693
|
-
automationId: slot.definition.id,
|
|
1694
|
-
endpointId: slot.definition.delivery.endpointId,
|
|
1695
|
-
kind: "automation",
|
|
1696
|
-
sourceMessageId: input.tickId,
|
|
1697
|
-
tenantKey: "automation",
|
|
1698
|
-
tickId: input.tickId
|
|
1699
|
-
},
|
|
1700
|
-
sessionKey: input.sessionKey,
|
|
1701
|
-
text: input.text
|
|
1702
|
-
})
|
|
1703
|
-
});
|
|
1704
|
-
});
|
|
1705
|
-
degraded ||= slotDegraded;
|
|
1706
|
-
}
|
|
1707
|
-
if (backgroundSessionSlot) {
|
|
1708
|
-
const slotDegraded = await startSlot(backgroundSessionSlot, true, async () => {
|
|
1709
|
-
if (!options.createBackgroundSession) throw new RivusDeploymentDaemonLifecycleError("deployment bootstrap does not provide Background Session adapters");
|
|
1710
|
-
return options.createBackgroundSession({
|
|
1711
|
-
agentIds: [...backgroundDefinitions.keys()],
|
|
1712
|
-
cancel: (input) => host.cancelBackgroundSession(input.agentId, input),
|
|
1713
|
-
config: backgroundSessionSlot.definition,
|
|
1714
|
-
run: (input) => host.handleBackgroundSession(input.agentId, input)
|
|
1715
|
-
});
|
|
1716
|
-
});
|
|
1717
|
-
degraded ||= slotDegraded;
|
|
1718
|
-
}
|
|
1719
|
-
lifecycle = degraded ? "degraded" : "running";
|
|
1720
|
-
assertRequiredReadiness();
|
|
1721
|
-
},
|
|
1722
|
-
status,
|
|
1723
|
-
stop: async () => {
|
|
1724
|
-
if (lifecycle === "stopped") {
|
|
1725
|
-
await runtimePool.disposeAll();
|
|
1726
|
-
return;
|
|
1727
|
-
}
|
|
1728
|
-
if (lifecycle !== "running" && lifecycle !== "degraded" && lifecycle !== "cleanup-required") throw new RivusDeploymentDaemonLifecycleError(`cannot stop deployment daemon while ${lifecycle}`);
|
|
1729
|
-
lifecycle = "stopping";
|
|
1730
|
-
const errors = [];
|
|
1731
|
-
await stopSlots(automationSlots, errors);
|
|
1732
|
-
if (backgroundSessionSlot) await stopSlots([backgroundSessionSlot], errors);
|
|
1733
|
-
await stopSlots(slots, errors);
|
|
1734
|
-
try {
|
|
1735
|
-
await runtimePool.disposeAll();
|
|
1736
|
-
} catch (error) {
|
|
1737
|
-
errors.push(error);
|
|
1738
|
-
}
|
|
1739
|
-
lifecycle = errors.length === 0 ? "stopped" : "cleanup-required";
|
|
1740
|
-
if (errors.length === 1) throw errors[0];
|
|
1741
|
-
if (errors.length > 1) throw new AggregateError(errors, "deployment daemon cleanup failed");
|
|
1742
|
-
}
|
|
2166
|
+
...adapted.concurrency ? { concurrency: adapted.concurrency } : {},
|
|
2167
|
+
...cancel ? { cancel: (input) => cancel(input).pipe(Effect.mapError(toError$3)) } : {},
|
|
2168
|
+
...dispose ? { dispose: () => dispose().pipe(Effect.mapError(toError$3)) } : {},
|
|
2169
|
+
run: (input) => adapted.run(input).pipe(Effect.mapError(toError$3)),
|
|
2170
|
+
...steer ? { steer: (input) => steer(input).pipe(Effect.mapError(toError$3)) } : {}
|
|
1743
2171
|
};
|
|
1744
2172
|
}
|
|
1745
|
-
function
|
|
2173
|
+
function toLifecycleAdapter(adapter) {
|
|
1746
2174
|
return {
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
2175
|
+
running: () => adapter.running(),
|
|
2176
|
+
start: () => Effect.tryPromise({
|
|
2177
|
+
try: () => Promise.resolve(adapter.start()),
|
|
2178
|
+
catch: toError$3
|
|
2179
|
+
}),
|
|
2180
|
+
stop: () => Effect.tryPromise({
|
|
2181
|
+
try: () => Promise.resolve(adapter.stop()),
|
|
2182
|
+
catch: toError$3
|
|
2183
|
+
})
|
|
1752
2184
|
};
|
|
1753
2185
|
}
|
|
1754
|
-
function
|
|
1755
|
-
return
|
|
2186
|
+
function toEffectProcessAgentRuntimeInput(input) {
|
|
2187
|
+
return toEffectAgentRuntimeInput(input);
|
|
1756
2188
|
}
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
2189
|
+
function toError$3(error) {
|
|
2190
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
2191
|
+
}
|
|
2192
|
+
//#endregion
|
|
2193
|
+
//#region src/adapters/deployment/plugin/rivus-plugin-module.ts
|
|
2194
|
+
function resolveRivusPluginModule(module) {
|
|
2195
|
+
const candidate = "default" in module ? module.default : module;
|
|
2196
|
+
return (typeof candidate === "function" ? Effect.tryPromise({
|
|
2197
|
+
try: () => Promise.resolve(candidate()),
|
|
2198
|
+
catch: toError$2
|
|
2199
|
+
}) : Effect.succeed(candidate)).pipe(Effect.flatMap((plugin) => isRivusPlugin(plugin) ? Effect.succeed(plugin) : Effect.fail(/* @__PURE__ */ new Error("plugin module default export is not a RivusPlugin or factory"))));
|
|
2200
|
+
}
|
|
2201
|
+
function isRivusPlugin(value) {
|
|
2202
|
+
return value !== null && typeof value === "object" && "manifest" in value && "register" in value && typeof value.register === "function";
|
|
2203
|
+
}
|
|
2204
|
+
function toError$2(error) {
|
|
2205
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
2206
|
+
}
|
|
2207
|
+
//#endregion
|
|
2208
|
+
//#region src/adapters/deployment/plugin/trusted-package-root.ts
|
|
2209
|
+
function findTrustedPackageRoot(packageManifestPath) {
|
|
2210
|
+
return Effect.tryPromise({
|
|
2211
|
+
try: async () => {
|
|
2212
|
+
const packageRoot = dirname(await realpath(packageManifestPath));
|
|
2213
|
+
let current = packageRoot;
|
|
2214
|
+
for (;;) {
|
|
2215
|
+
if (basename(current) === "node_modules") return current;
|
|
2216
|
+
const parent = dirname(current);
|
|
2217
|
+
if (parent === current) return packageRoot;
|
|
2218
|
+
current = parent;
|
|
2219
|
+
}
|
|
2220
|
+
},
|
|
2221
|
+
catch: (error) => error instanceof Error ? error : new Error(String(error))
|
|
2222
|
+
});
|
|
2223
|
+
}
|
|
2224
|
+
//#endregion
|
|
2225
|
+
//#region src/platform/filesystem/path-boundary.ts
|
|
2226
|
+
function isPathWithin(root, candidate) {
|
|
2227
|
+
const child = relative(root, candidate);
|
|
2228
|
+
return child === "" || !child.startsWith(`..${sep}`) && child !== ".." && !isAbsolute(child);
|
|
2229
|
+
}
|
|
2230
|
+
function assertPathWithin(root, candidate, message) {
|
|
2231
|
+
if (!isPathWithin(root, candidate)) throw new Error(message);
|
|
2232
|
+
}
|
|
2233
|
+
//#endregion
|
|
2234
|
+
//#region src/adapters/deployment/plugin/trusted-module-path.ts
|
|
2235
|
+
function validateTrustedModulePath(trustedRoot, resolvedPath, subject) {
|
|
2236
|
+
return Effect.try({
|
|
2237
|
+
try: () => {
|
|
2238
|
+
assertPathWithin(trustedRoot, resolvedPath, `${subject} resolves outside trusted module root: ${resolvedPath}`);
|
|
2239
|
+
return resolvedPath;
|
|
2240
|
+
},
|
|
2241
|
+
catch: (failure) => failure instanceof Error ? failure : new Error(String(failure))
|
|
2242
|
+
});
|
|
2243
|
+
}
|
|
2244
|
+
//#endregion
|
|
2245
|
+
//#region src/adapters/deployment/plugin/node-rivus-plugin-module-loader.ts
|
|
2246
|
+
function createNodeRivusPluginModuleLoader(options = {}) {
|
|
2247
|
+
return { load: (request) => loadNodeRivusPluginModule(request, options).pipe(Effect.flatMap(resolveRivusPluginModule)) };
|
|
2248
|
+
}
|
|
2249
|
+
function loadNodeRivusPluginModule(request, options = {}) {
|
|
2250
|
+
return resolveNodeRivusPluginModulePath(request, options).pipe(Effect.flatMap((resolvedRealpath) => Effect.tryPromise({
|
|
2251
|
+
try: () => import(pathToFileURL(resolvedRealpath).href),
|
|
2252
|
+
catch: toError$1
|
|
2253
|
+
})));
|
|
2254
|
+
}
|
|
2255
|
+
function resolveNodeRivusPluginModulePath(request, options = {}) {
|
|
2256
|
+
return Effect.gen(function* () {
|
|
2257
|
+
const deploymentRoot = yield* Effect.tryPromise({
|
|
2258
|
+
try: () => realpath(request.deploymentRoot),
|
|
2259
|
+
catch: toError$1
|
|
2260
|
+
});
|
|
2261
|
+
const relativeModule = request.module.startsWith("./") || request.module.startsWith("../");
|
|
2262
|
+
const resolutionManifest = relativeModule ? join(deploymentRoot, "package.json") : options.packageManifestPath ?? join(deploymentRoot, "package.json");
|
|
2263
|
+
const resolved = yield* Effect.try({
|
|
2264
|
+
try: () => createRequire(resolutionManifest).resolve(request.module),
|
|
2265
|
+
catch: toError$1
|
|
2266
|
+
});
|
|
2267
|
+
const resolvedRealpath = yield* Effect.tryPromise({
|
|
2268
|
+
try: () => realpath(resolved),
|
|
2269
|
+
catch: toError$1
|
|
2270
|
+
});
|
|
2271
|
+
return yield* validateTrustedModulePath(relativeModule ? deploymentRoot : options.packageManifestPath ? yield* findTrustedPackageRoot(options.packageManifestPath) : deploymentRoot, resolvedRealpath, `plugin module ${request.module}`);
|
|
2272
|
+
});
|
|
2273
|
+
}
|
|
2274
|
+
function toError$1(error) {
|
|
2275
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
2276
|
+
}
|
|
2277
|
+
//#endregion
|
|
2278
|
+
//#region src/modules/deployment-control/module.ts
|
|
2279
|
+
function resolveRivusDeploymentModule(options) {
|
|
2280
|
+
return resolveRivusDeployment(options);
|
|
2281
|
+
}
|
|
2282
|
+
function createRivusDeploymentControlModule(options) {
|
|
2283
|
+
return Effect.gen(function* () {
|
|
2284
|
+
const deployment = yield* resolveRivusDeploymentModule(options);
|
|
2285
|
+
return yield* createRivusDeploymentControl({
|
|
2286
|
+
agentHostFactory: options.agentHostFactory,
|
|
2287
|
+
...options.automationFactory ? { automationFactory: options.automationFactory } : {},
|
|
2288
|
+
...options.backgroundSessionFactory ? { backgroundSessionFactory: options.backgroundSessionFactory } : {},
|
|
2289
|
+
deployment,
|
|
2290
|
+
deploymentRoot: options.deploymentRoot,
|
|
2291
|
+
endpointFactory: options.endpointFactory,
|
|
2292
|
+
...options.initialInstanceRecords ? { initialInstanceRecords: options.initialInstanceRecords } : {},
|
|
2293
|
+
...options.projectSpaceResolver ? { projectSpaceResolver: options.projectSpaceResolver } : {},
|
|
2294
|
+
runtimeFactory: options.runtimeFactory
|
|
2295
|
+
});
|
|
2296
|
+
});
|
|
2297
|
+
}
|
|
2298
|
+
//#endregion
|
|
2299
|
+
//#region src/modules/project-space/application/workspace/workspace-instructions-resolver.ts
|
|
2300
|
+
function createWorkspaceInstructionsResolver(source) {
|
|
2301
|
+
return { resolve: (request) => Effect.gen(function* () {
|
|
2302
|
+
yield* Effect.try({
|
|
2303
|
+
catch: (error) => error,
|
|
2304
|
+
try: () => validateWorkspaceInstructionsBudget(request.maxBytes)
|
|
2305
|
+
});
|
|
2306
|
+
const loaded = yield* source.load({
|
|
2307
|
+
maxBytes: request.maxBytes,
|
|
2308
|
+
...request.targetPath !== void 0 ? { targetPath: request.targetPath } : {},
|
|
2309
|
+
workingDirectory: request.workingDirectory,
|
|
2310
|
+
workspaceRoot: request.workspaceRoot
|
|
2311
|
+
});
|
|
2312
|
+
return assembleWorkspaceInstructions({
|
|
2313
|
+
diagnostics: loaded.diagnostics,
|
|
2314
|
+
maxBytes: request.maxBytes,
|
|
2315
|
+
...request.operation ? { operation: request.operation } : {},
|
|
2316
|
+
...request.presentedDigests ? { presentedDigests: request.presentedDigests } : {},
|
|
2317
|
+
sources: loaded.sources,
|
|
2318
|
+
...request.targetPath !== void 0 ? { targetPath: request.targetPath } : {}
|
|
2319
|
+
});
|
|
2320
|
+
}) };
|
|
2321
|
+
}
|
|
2322
|
+
//#endregion
|
|
2323
|
+
//#region src/modules/project-space/infrastructure/workspace/node-workspace-root-registry.ts
|
|
2324
|
+
const workspaceRoots = /* @__PURE__ */ new WeakMap();
|
|
2325
|
+
async function createWorkspaceRootHandle(rootPath) {
|
|
2326
|
+
const resolved = await realpath(rootPath);
|
|
2327
|
+
if (!(await stat(resolved)).isDirectory()) throw new InvalidWorkspaceRoot("workspace root must be a directory");
|
|
2328
|
+
const handle = Object.freeze({ id: `workspace:${createHash("sha256").update(resolved).digest("hex")}` });
|
|
2329
|
+
workspaceRoots.set(handle, resolved);
|
|
2330
|
+
return handle;
|
|
2331
|
+
}
|
|
2332
|
+
function resolveWorkspaceRootPath(handle) {
|
|
2333
|
+
const rootPath = workspaceRoots.get(handle);
|
|
2334
|
+
if (!rootPath) throw new InvalidWorkspaceRoot("workspace root handle was not created by this host");
|
|
2335
|
+
return rootPath;
|
|
2336
|
+
}
|
|
2337
|
+
//#endregion
|
|
2338
|
+
//#region src/modules/project-space/infrastructure/instructions/agents-md-instructions-source.ts
|
|
2339
|
+
function createAgentsMdInstructionsSource() {
|
|
2340
|
+
return { load: (request) => Effect.tryPromise({
|
|
2341
|
+
catch: (error) => error,
|
|
2342
|
+
try: () => loadAgentsMdInstructions(request)
|
|
2343
|
+
}) };
|
|
2344
|
+
}
|
|
2345
|
+
async function loadAgentsMdInstructions(request) {
|
|
2346
|
+
const rootPath = resolveWorkspaceRootPath(request.workspaceRoot);
|
|
2347
|
+
const workingDirectory = await resolveBoundDirectory(rootPath, request.workingDirectory, false);
|
|
2348
|
+
const targetDirectory = request.targetPath ? await resolveBoundDirectory(rootPath, request.targetPath, true) : workingDirectory;
|
|
2349
|
+
if (!isPathWithin(rootPath, workingDirectory) || !isPathWithin(rootPath, targetDirectory)) throw new InvalidWorkspaceRoot("workspace path escapes the bound root");
|
|
2350
|
+
const discovered = [];
|
|
2351
|
+
const diagnostics = [];
|
|
2352
|
+
for (const directory of directoriesFromRoot(rootPath, targetDirectory)) {
|
|
2353
|
+
const result = await readSource(rootPath, join(directory, "AGENTS.md"), request.maxBytes);
|
|
2354
|
+
if (result?.source) discovered.push(result.source);
|
|
2355
|
+
if (result?.diagnostic) diagnostics.push(result.diagnostic);
|
|
2356
|
+
}
|
|
2357
|
+
return Object.freeze({
|
|
2358
|
+
diagnostics: Object.freeze(diagnostics),
|
|
2359
|
+
sources: Object.freeze(discovered)
|
|
2360
|
+
});
|
|
2361
|
+
}
|
|
2362
|
+
async function resolveBoundDirectory(rootPath, path, useParentForFile) {
|
|
2363
|
+
validateRelativePath$1(path);
|
|
2364
|
+
const candidate = resolve(rootPath, path || ".");
|
|
2365
|
+
if (!isPathWithin(rootPath, candidate)) throw new InvalidWorkspaceRoot("workspace path escapes the bound root");
|
|
2366
|
+
let existing = candidate;
|
|
2367
|
+
let metadata;
|
|
2368
|
+
while (isPathWithin(rootPath, existing)) try {
|
|
2369
|
+
metadata = await stat(existing);
|
|
2370
|
+
break;
|
|
2371
|
+
} catch (error) {
|
|
2372
|
+
if (!isMissing(error)) throw error;
|
|
2373
|
+
if (existing === rootPath) break;
|
|
2374
|
+
existing = dirname(existing);
|
|
1761
2375
|
}
|
|
1762
|
-
if (!
|
|
1763
|
-
|
|
1764
|
-
|
|
2376
|
+
if (!metadata) throw new InvalidWorkspaceRoot(`workspace path does not have an existing parent: ${path}`);
|
|
2377
|
+
const resolvedExisting = await realpath(existing);
|
|
2378
|
+
if (!isPathWithin(rootPath, resolvedExisting)) throw new InvalidWorkspaceRoot("workspace path resolves outside the bound root");
|
|
2379
|
+
if (existing !== candidate) {
|
|
2380
|
+
if (!useParentForFile) throw new InvalidWorkspaceRoot(`working directory does not exist: ${path}`);
|
|
2381
|
+
return resolvedExisting;
|
|
1765
2382
|
}
|
|
1766
|
-
|
|
1767
|
-
|
|
2383
|
+
if (metadata.isDirectory()) return resolvedExisting;
|
|
2384
|
+
if (!useParentForFile || !metadata.isFile()) throw new InvalidWorkspaceRoot(`workspace path must resolve to a directory: ${path}`);
|
|
2385
|
+
return dirname(resolvedExisting);
|
|
2386
|
+
}
|
|
2387
|
+
async function readSource(rootPath, sourcePath, maxBytes) {
|
|
2388
|
+
let metadata;
|
|
1768
2389
|
try {
|
|
1769
|
-
|
|
1770
|
-
await slot.adapter.start();
|
|
1771
|
-
slot.lifecycle = "running";
|
|
1772
|
-
return false;
|
|
2390
|
+
metadata = await lstat(sourcePath);
|
|
1773
2391
|
} catch (error) {
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
return true;
|
|
2392
|
+
if (isMissing(error)) return void 0;
|
|
2393
|
+
throw error;
|
|
1777
2394
|
}
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
2395
|
+
const relativePath = toRelative(rootPath, sourcePath);
|
|
2396
|
+
if (metadata.isSymbolicLink() || !metadata.isFile()) throw new WorkspaceInstructionsSourceError(relativePath, "source must be a regular non-symlink file");
|
|
2397
|
+
if (metadata.size > maxBytes) return { diagnostic: {
|
|
2398
|
+
code: "workspace-instructions-source-omitted",
|
|
2399
|
+
reason: `source size ${metadata.size} exceeds byte budget ${maxBytes}`,
|
|
2400
|
+
relativePath
|
|
2401
|
+
} };
|
|
2402
|
+
const resolvedSource = await realpath(sourcePath);
|
|
2403
|
+
if (!isPathWithin(rootPath, resolvedSource)) throw new WorkspaceInstructionsSourceError(relativePath, "source resolves outside workspace root");
|
|
2404
|
+
const bytes = await readFile(resolvedSource);
|
|
2405
|
+
let content;
|
|
2406
|
+
try {
|
|
2407
|
+
content = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
2408
|
+
} catch {
|
|
2409
|
+
throw new WorkspaceInstructionsSourceError(relativePath, "source is not valid UTF-8");
|
|
2410
|
+
}
|
|
2411
|
+
return { source: Object.freeze({
|
|
2412
|
+
bytes: bytes.byteLength,
|
|
2413
|
+
content,
|
|
2414
|
+
digest: createSourceDigest(bytes),
|
|
2415
|
+
relativePath
|
|
2416
|
+
}) };
|
|
2417
|
+
}
|
|
2418
|
+
function validateRelativePath$1(path) {
|
|
2419
|
+
if (isAbsolute(path) || path.split(/[\\/]/).includes("..")) throw new InvalidWorkspaceRoot("workspace path must be relative and cannot contain ..");
|
|
2420
|
+
}
|
|
2421
|
+
function directoriesFromRoot(rootPath, targetDirectory) {
|
|
2422
|
+
const suffix = relative(rootPath, targetDirectory);
|
|
2423
|
+
const directories = [rootPath];
|
|
2424
|
+
if (!suffix) return directories;
|
|
2425
|
+
let current = rootPath;
|
|
2426
|
+
for (const segment of suffix.split(sep)) {
|
|
2427
|
+
current = join(current, segment);
|
|
2428
|
+
directories.push(current);
|
|
1792
2429
|
}
|
|
2430
|
+
return directories;
|
|
2431
|
+
}
|
|
2432
|
+
function toRelative(rootPath, path) {
|
|
2433
|
+
return relative(rootPath, path).split(sep).join("/");
|
|
2434
|
+
}
|
|
2435
|
+
function createSourceDigest(bytes) {
|
|
2436
|
+
return `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
|
|
2437
|
+
}
|
|
2438
|
+
function isMissing(error) {
|
|
2439
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
1793
2440
|
}
|
|
1794
2441
|
//#endregion
|
|
1795
|
-
//#region src/
|
|
1796
|
-
var InvalidRivusProjectSpace = class extends Error {
|
|
1797
|
-
name = "InvalidRivusProjectSpace";
|
|
1798
|
-
};
|
|
1799
|
-
//#endregion
|
|
1800
|
-
//#region src/infrastructure/workspace/rivus-project-space.ts
|
|
2442
|
+
//#region src/modules/project-space/infrastructure/space/node-project-space-resolver.ts
|
|
1801
2443
|
async function resolveRivusProjectSpace(input) {
|
|
2444
|
+
validateRivusProjectSpaceDeployment(input.declaration);
|
|
1802
2445
|
const root = await resolveContainedDirectory(await resolveExistingDirectory(input.deploymentRoot, "deployment root"), input.declaration.root, "Project Space root");
|
|
1803
2446
|
const workingDirectory = await resolveContainedDirectory(root, input.declaration.workingDirectory, "Project Space working directory");
|
|
1804
2447
|
const skillPaths = await Promise.all(input.declaration.skills.sources.map((source) => resolveContainedPath(root, source, "Project Space Skill source")));
|
|
@@ -1860,25 +2503,21 @@ function validateRelativePath(path, owner) {
|
|
|
1860
2503
|
if (!path.trim() || path.includes("\0") || isAbsolute(path)) throw new InvalidRivusProjectSpace(`${owner} must be a non-empty relative path`);
|
|
1861
2504
|
}
|
|
1862
2505
|
function assertContained(base, candidate, owner) {
|
|
1863
|
-
|
|
1864
|
-
if (relation === "" || !relation.startsWith("..") && !isAbsolute(relation)) return;
|
|
2506
|
+
if (isPathWithin(base, candidate)) return;
|
|
1865
2507
|
throw new InvalidRivusProjectSpace(`${owner} escapes its trusted root`);
|
|
1866
2508
|
}
|
|
1867
2509
|
//#endregion
|
|
1868
|
-
//#region src/
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
manifest,
|
|
1880
|
-
resolveProjectSpace: (input) => resolveRivusProjectSpace(input)
|
|
1881
|
-
});
|
|
2510
|
+
//#region src/modules/project-space/module.ts
|
|
2511
|
+
function createAgentsMdInstructionsResolver() {
|
|
2512
|
+
return createWorkspaceInstructionsResolver(createAgentsMdInstructionsSource());
|
|
2513
|
+
}
|
|
2514
|
+
//#endregion
|
|
2515
|
+
//#region src/composition/runtime/deployment-process-effect-runner.ts
|
|
2516
|
+
async function runDeploymentProcessEffect(effect) {
|
|
2517
|
+
const exit = await Effect.runPromiseExit(effect);
|
|
2518
|
+
if (Exit.isSuccess(exit)) return exit.value;
|
|
2519
|
+
const failure = Cause.failureOption(exit.cause);
|
|
2520
|
+
throw Option.isSome(failure) ? failure.value : Cause.squash(exit.cause);
|
|
1882
2521
|
}
|
|
1883
2522
|
//#endregion
|
|
1884
2523
|
//#region src/composition/rivus-deployment-cli-process.ts
|
|
@@ -1893,14 +2532,24 @@ async function createRivusDeploymentCliProcess(factory, context) {
|
|
|
1893
2532
|
};
|
|
1894
2533
|
let daemon;
|
|
1895
2534
|
try {
|
|
1896
|
-
daemon = await
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
2535
|
+
daemon = await runDeploymentProcessEffect(loadRivusDeploymentManifest(context.manifestPath).pipe(Effect.flatMap((manifest) => {
|
|
2536
|
+
return createRivusDeploymentControlModule({
|
|
2537
|
+
...createProcessDeploymentControlPorts({
|
|
2538
|
+
...adapters.createAutomation ? { createAutomation: adapters.createAutomation } : {},
|
|
2539
|
+
...adapters.createBackgroundSession ? { createBackgroundSession: adapters.createBackgroundSession } : {},
|
|
2540
|
+
createEndpoint: adapters.createEndpoint,
|
|
2541
|
+
createRuntime: adapters.createRuntime
|
|
2542
|
+
}, manifest.backgroundSessions?.enabled === true, runDeploymentProcessEffect),
|
|
2543
|
+
deploymentRoot: dirname(context.manifestPath),
|
|
2544
|
+
...adapters.initialInstanceRecords ? { initialInstanceRecords: adapters.initialInstanceRecords } : {},
|
|
2545
|
+
manifest,
|
|
2546
|
+
pluginLoader: createNodeRivusPluginModuleLoader(context.pluginPackageManifestPath ? { packageManifestPath: context.pluginPackageManifestPath } : {}),
|
|
2547
|
+
projectSpaceResolver: { resolve: (input) => Effect.tryPromise({
|
|
2548
|
+
try: () => resolveRivusProjectSpace(input),
|
|
2549
|
+
catch: toError
|
|
2550
|
+
}) }
|
|
2551
|
+
});
|
|
2552
|
+
})));
|
|
1904
2553
|
} catch (constructionError) {
|
|
1905
2554
|
try {
|
|
1906
2555
|
await disposeAdapters();
|
|
@@ -1912,11 +2561,11 @@ async function createRivusDeploymentCliProcess(factory, context) {
|
|
|
1912
2561
|
const start = async () => {
|
|
1913
2562
|
if (adaptersDisposed) throw new Error("Rivus deployment process cannot restart after its adapters have been disposed");
|
|
1914
2563
|
try {
|
|
1915
|
-
await daemon.start();
|
|
2564
|
+
await runDeploymentProcessEffect(daemon.start());
|
|
1916
2565
|
} catch (startError) {
|
|
1917
2566
|
const cleanupErrors = [];
|
|
1918
2567
|
try {
|
|
1919
|
-
await daemon.stop();
|
|
2568
|
+
await runDeploymentProcessEffect(daemon.stop());
|
|
1920
2569
|
} catch (error) {
|
|
1921
2570
|
cleanupErrors.push(error);
|
|
1922
2571
|
}
|
|
@@ -1932,7 +2581,7 @@ async function createRivusDeploymentCliProcess(factory, context) {
|
|
|
1932
2581
|
const stop = async () => {
|
|
1933
2582
|
const errors = [];
|
|
1934
2583
|
try {
|
|
1935
|
-
await daemon.stop();
|
|
2584
|
+
await runDeploymentProcessEffect(daemon.stop());
|
|
1936
2585
|
} catch (error) {
|
|
1937
2586
|
errors.push(error);
|
|
1938
2587
|
}
|
|
@@ -1973,7 +2622,7 @@ async function createRivusDeploymentCliProcess(factory, context) {
|
|
|
1973
2622
|
promptText: (command) => Effect.tryPromise({
|
|
1974
2623
|
try: async () => {
|
|
1975
2624
|
if (adaptersDisposed) throw new Error("Rivus deployment process cannot restart after its adapters have been disposed");
|
|
1976
|
-
return readPromptFinalText(await daemon.runDefaultAgent({
|
|
2625
|
+
return readPromptFinalText(await runDeploymentProcessEffect(daemon.runDefaultAgent({
|
|
1977
2626
|
invocation: {
|
|
1978
2627
|
allowedActorOpenIds: [],
|
|
1979
2628
|
endpointId: daemon.deployment.manifest.defaultEndpointId,
|
|
@@ -1990,7 +2639,7 @@ async function createRivusDeploymentCliProcess(factory, context) {
|
|
|
1990
2639
|
},
|
|
1991
2640
|
sessionKey: command.sessionKey,
|
|
1992
2641
|
text: command.text
|
|
1993
|
-
}));
|
|
2642
|
+
})));
|
|
1994
2643
|
},
|
|
1995
2644
|
catch: (error) => error
|
|
1996
2645
|
})
|
|
@@ -2001,7 +2650,7 @@ async function createRivusDeploymentCliProcess(factory, context) {
|
|
|
2001
2650
|
if (!daemon.running()) await start();
|
|
2002
2651
|
},
|
|
2003
2652
|
catch: (error) => error
|
|
2004
|
-
}).pipe(Effect.flatMap(() => replayReceiveMessage((input) => daemon.handleDefault(input), payload, options)));
|
|
2653
|
+
}).pipe(Effect.flatMap(() => replayReceiveMessage((input) => runDeploymentProcessEffect(daemon.handleDefault(toEffectProcessAgentRuntimeInput(input))), payload, options)));
|
|
2005
2654
|
return process;
|
|
2006
2655
|
}
|
|
2007
2656
|
function readPromptFinalText(result) {
|
|
@@ -2009,6 +2658,9 @@ function readPromptFinalText(result) {
|
|
|
2009
2658
|
if (typeof result === "object" && result !== null && "finalText" in result && typeof result.finalText === "string") return result.finalText;
|
|
2010
2659
|
throw new Error("Default deployment prompt result must be a string or contain finalText");
|
|
2011
2660
|
}
|
|
2661
|
+
function toError(error) {
|
|
2662
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
2663
|
+
}
|
|
2012
2664
|
//#endregion
|
|
2013
2665
|
//#region src/composition/rivus-recovery-cli.ts
|
|
2014
2666
|
function createRivusRecoveryCliParser() {
|
|
@@ -2178,6 +2830,10 @@ const USAGE = `Usage: rivus --bootstrap <module> [--manifest <rivus.config.json>
|
|
|
2178
2830
|
Starts a local Rivus Agent daemon from an injected bootstrap module.
|
|
2179
2831
|
|
|
2180
2832
|
Project commands:
|
|
2833
|
+
rivus setup [directory]
|
|
2834
|
+
rivus start
|
|
2835
|
+
rivus status
|
|
2836
|
+
rivus check-config
|
|
2181
2837
|
rivus init [directory]
|
|
2182
2838
|
rivus doctor [directory] [--env-file <path>]
|
|
2183
2839
|
|
|
@@ -2233,6 +2889,7 @@ Options:
|
|
|
2233
2889
|
--help Show this help
|
|
2234
2890
|
|
|
2235
2891
|
Environment:
|
|
2892
|
+
RIVUS_HOME selects Rivus Home (default ~/.rivus-agent) for Home commands.
|
|
2236
2893
|
RIVUS_BOOTSTRAP_MODULE may be used instead of --bootstrap.
|
|
2237
2894
|
FEISHU_APP_ID and FEISHU_APP_SECRET are required by the default config loader.
|
|
2238
2895
|
FEISHU_CARD_STREAM_LEASE_MS shortens or extends the proactive CardKit rollover threshold.
|
|
@@ -2241,7 +2898,7 @@ Environment:
|
|
|
2241
2898
|
const DEFAULT_WAIT_RECEIVE_TIMEOUT_MS = 3e4;
|
|
2242
2899
|
const DEFAULT_WAIT_RECEIVE_POLL_MS = 500;
|
|
2243
2900
|
function runRivusDaemonCli(options) {
|
|
2244
|
-
return Effect.
|
|
2901
|
+
return Effect.gen(function* () {
|
|
2245
2902
|
const parsed = parseArgs(options.argv);
|
|
2246
2903
|
if (parsed.help) {
|
|
2247
2904
|
options.stdout.write(USAGE);
|
|
@@ -2251,36 +2908,38 @@ function runRivusDaemonCli(options) {
|
|
|
2251
2908
|
options.stderr.write(`${parsed.error}\n\n${USAGE}`);
|
|
2252
2909
|
return 1;
|
|
2253
2910
|
}
|
|
2254
|
-
if (parsed.statusUrl)
|
|
2255
|
-
const
|
|
2911
|
+
if (parsed.statusUrl) {
|
|
2912
|
+
const statusUrl = parsed.statusUrl;
|
|
2913
|
+
const readStatus = () => Effect.tryPromise({
|
|
2914
|
+
try: () => fetchLiveStatus(statusUrl),
|
|
2915
|
+
catch: (error) => error
|
|
2916
|
+
});
|
|
2917
|
+
const status = parsed.waitReceive ? yield* waitForReceiveStatus(readStatus, parsed.waitReceive, {
|
|
2256
2918
|
...parsed.waitReceiveMessageId ? { messageId: parsed.waitReceiveMessageId } : {},
|
|
2257
2919
|
...parsed.waitReceiveObservedAfter ? { observedAfter: parsed.waitReceiveObservedAfter } : {},
|
|
2258
2920
|
pollMs: parsed.waitPollMs ?? DEFAULT_WAIT_RECEIVE_POLL_MS,
|
|
2259
2921
|
...parsed.waitReceiveText ? { text: parsed.waitReceiveText } : {},
|
|
2260
2922
|
timeoutMs: parsed.waitTimeoutMs ?? DEFAULT_WAIT_RECEIVE_TIMEOUT_MS
|
|
2261
|
-
}) :
|
|
2923
|
+
}) : yield* readStatus();
|
|
2262
2924
|
options.stdout.write(`${JSON.stringify(status, null, 2)}\n`);
|
|
2263
2925
|
return 0;
|
|
2264
|
-
} catch (error) {
|
|
2265
|
-
options.stderr.write(`${formatCliError(error)}\n`);
|
|
2266
|
-
return 1;
|
|
2267
2926
|
}
|
|
2268
|
-
if (parsed.printOpenClawEnvPath)
|
|
2269
|
-
const
|
|
2927
|
+
if (parsed.printOpenClawEnvPath) {
|
|
2928
|
+
const openClawConfigPath = parsed.printOpenClawEnvPath;
|
|
2929
|
+
const result = yield* Effect.tryPromise({
|
|
2930
|
+
try: async () => {
|
|
2931
|
+
return createRivusEnvFromOpenClawConfig(JSON.parse(await readFile(openClawConfigPath, "utf8")), parsed.piApiKeyFile ? { piApiKeyFile: parsed.piApiKeyFile } : {});
|
|
2932
|
+
},
|
|
2933
|
+
catch: (error) => error
|
|
2934
|
+
});
|
|
2270
2935
|
for (const warning of result.warnings) options.stderr.write(`Warning: ${warning}\n`);
|
|
2271
2936
|
options.stdout.write(formatRivusEnvFile(result.env));
|
|
2272
2937
|
return 0;
|
|
2273
|
-
} catch (error) {
|
|
2274
|
-
options.stderr.write(`${formatCliError(error)}\n`);
|
|
2275
|
-
return 1;
|
|
2276
|
-
}
|
|
2277
|
-
let env;
|
|
2278
|
-
try {
|
|
2279
|
-
env = await loadCliEnv(parsed.envFilePath, options.env);
|
|
2280
|
-
} catch (error) {
|
|
2281
|
-
options.stderr.write(`${formatCliError(error)}\n`);
|
|
2282
|
-
return 1;
|
|
2283
2938
|
}
|
|
2939
|
+
const env = yield* Effect.tryPromise({
|
|
2940
|
+
try: () => loadCliEnv(parsed.envFilePath, options.env),
|
|
2941
|
+
catch: (error) => error
|
|
2942
|
+
});
|
|
2284
2943
|
const bootstrapSpecifier = parsed.bootstrap ?? env.RIVUS_BOOTSTRAP_MODULE?.trim();
|
|
2285
2944
|
if (!parsed.checkConfig && !bootstrapSpecifier) {
|
|
2286
2945
|
options.stderr.write(`Missing --bootstrap <module> or RIVUS_BOOTSTRAP_MODULE\n\n${USAGE}`);
|
|
@@ -2288,17 +2947,18 @@ function runRivusDaemonCli(options) {
|
|
|
2288
2947
|
}
|
|
2289
2948
|
let legacyConfig;
|
|
2290
2949
|
if (parsed.manifestPath) {
|
|
2291
|
-
if (parsed.checkConfig)
|
|
2292
|
-
const
|
|
2293
|
-
|
|
2950
|
+
if (parsed.checkConfig) {
|
|
2951
|
+
const manifestPath = parsed.manifestPath;
|
|
2952
|
+
const manifest = yield* loadRivusDeploymentManifest(manifestPath);
|
|
2953
|
+
yield* Effect.try({
|
|
2954
|
+
try: () => validateRivusDeploymentManifest(manifest),
|
|
2955
|
+
catch: (error) => error
|
|
2956
|
+
});
|
|
2294
2957
|
options.stdout.write(`${JSON.stringify(toRedactedDeploymentManifest(manifest), null, 2)}\n`);
|
|
2295
2958
|
return 0;
|
|
2296
|
-
} catch (error) {
|
|
2297
|
-
options.stderr.write(`${formatCliError(error)}\n`);
|
|
2298
|
-
return 1;
|
|
2299
2959
|
}
|
|
2300
2960
|
} else {
|
|
2301
|
-
const configExit =
|
|
2961
|
+
const configExit = yield* Effect.exit(loadRivusDaemonConfig(env));
|
|
2302
2962
|
if (configExit._tag === "Failure") {
|
|
2303
2963
|
options.stderr.write(`${configExit.cause.toString()}\n`);
|
|
2304
2964
|
return 1;
|
|
@@ -2313,113 +2973,132 @@ function runRivusDaemonCli(options) {
|
|
|
2313
2973
|
options.stderr.write(`Missing --bootstrap <module> or RIVUS_BOOTSTRAP_MODULE\n\n${USAGE}`);
|
|
2314
2974
|
return 1;
|
|
2315
2975
|
}
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2976
|
+
const loadBootstrap = options.loadBootstrap ?? ((specifier) => import(specifier));
|
|
2977
|
+
const module = yield* Effect.tryPromise({
|
|
2978
|
+
try: () => loadBootstrap(bootstrapSpecifier),
|
|
2979
|
+
catch: (error) => error
|
|
2980
|
+
});
|
|
2981
|
+
const daemon = yield* Effect.tryPromise({
|
|
2982
|
+
try: () => parsed.manifestPath ? createCliDeploymentDaemon(module.createRivusDeploymentAdapters, {
|
|
2319
2983
|
argv: options.argv,
|
|
2320
2984
|
env,
|
|
2321
|
-
manifestPath: parsed.manifestPath
|
|
2322
|
-
|
|
2985
|
+
manifestPath: parsed.manifestPath,
|
|
2986
|
+
...options.pluginPackageManifestPath ? { pluginPackageManifestPath: options.pluginPackageManifestPath } : {}
|
|
2987
|
+
}) : createCliLegacyDaemon(module, {
|
|
2323
2988
|
argv: options.argv,
|
|
2324
2989
|
config: legacyConfig,
|
|
2325
2990
|
env
|
|
2326
|
-
})
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2991
|
+
}),
|
|
2992
|
+
catch: (error) => error
|
|
2993
|
+
});
|
|
2994
|
+
if (parsed.recoveryCommand) {
|
|
2995
|
+
const recoveryCommand = parsed.recoveryCommand;
|
|
2996
|
+
return yield* runOneShotDaemon(daemon, () => Effect.gen(function* () {
|
|
2997
|
+
if (!hasRecoveryRunner(daemon)) {
|
|
2998
|
+
options.stderr.write(`Bootstrap daemon does not expose openRecoveryControl()\n`);
|
|
2999
|
+
return 1;
|
|
3000
|
+
}
|
|
3001
|
+
const control = yield* daemon.openRecoveryControl();
|
|
3002
|
+
const result = yield* Effect.tryPromise({
|
|
3003
|
+
try: () => runRivusRecoveryCliCommand(control, recoveryCommand),
|
|
3004
|
+
catch: (error) => error
|
|
2337
3005
|
});
|
|
3006
|
+
options.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
3007
|
+
return 0;
|
|
3008
|
+
}));
|
|
3009
|
+
}
|
|
3010
|
+
if (parsed.status) return yield* runOneShotDaemon(daemon, () => Effect.gen(function* () {
|
|
3011
|
+
if (!hasStatusReporter(daemon)) {
|
|
3012
|
+
options.stderr.write(`Bootstrap daemon does not expose status()\n`);
|
|
3013
|
+
return 1;
|
|
2338
3014
|
}
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
3015
|
+
const status = yield* daemon.status();
|
|
3016
|
+
options.stdout.write(`${JSON.stringify(status, null, 2)}\n`);
|
|
3017
|
+
return 0;
|
|
3018
|
+
}));
|
|
3019
|
+
if (parsed.prompt !== void 0) {
|
|
3020
|
+
const prompt = parsed.prompt;
|
|
3021
|
+
return yield* runOneShotDaemon(daemon, () => Effect.gen(function* () {
|
|
3022
|
+
if (!hasPromptRunner(daemon)) {
|
|
3023
|
+
options.stderr.write(`Bootstrap daemon does not expose promptText(command)\n`);
|
|
2342
3024
|
return 1;
|
|
2343
3025
|
}
|
|
2344
|
-
const
|
|
2345
|
-
|
|
3026
|
+
const text = yield* daemon.promptText({
|
|
3027
|
+
sessionKey: parsed.sessionKey ?? daemon.defaultSessionKey ?? `local:${legacyConfig.agentId}:cli`,
|
|
3028
|
+
text: prompt
|
|
3029
|
+
});
|
|
3030
|
+
options.stdout.write(`${text}\n`);
|
|
2346
3031
|
return 0;
|
|
2347
|
-
});
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
}
|
|
2359
|
-
|
|
2360
|
-
|
|
3032
|
+
}));
|
|
3033
|
+
}
|
|
3034
|
+
if (parsed.replayFeishuText !== void 0) {
|
|
3035
|
+
const replayFeishuText = parsed.replayFeishuText;
|
|
3036
|
+
return yield* runOneShotDaemon(daemon, () => Effect.gen(function* () {
|
|
3037
|
+
if (!hasFeishuReplayRunner(daemon)) {
|
|
3038
|
+
options.stderr.write(`Bootstrap daemon does not expose replayReceiveMessage(payload)\n`);
|
|
3039
|
+
return 1;
|
|
3040
|
+
}
|
|
3041
|
+
const payload = createSyntheticFeishuTextReplayPayload(replayFeishuText, {
|
|
3042
|
+
...parsed.feishuReplayChatId !== void 0 ? { chatId: parsed.feishuReplayChatId } : {},
|
|
3043
|
+
...parsed.feishuReplayMessageId !== void 0 ? { messageId: parsed.feishuReplayMessageId } : {},
|
|
3044
|
+
...parsed.feishuReplayTenantKey !== void 0 ? { tenantKey: parsed.feishuReplayTenantKey } : {},
|
|
3045
|
+
...parsed.feishuReplayThreadId !== void 0 ? { threadId: parsed.feishuReplayThreadId } : {}
|
|
2361
3046
|
});
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
options.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
2378
|
-
return 0;
|
|
3047
|
+
const result = yield* daemon.replayReceiveMessage(payload, { sideEffects: "disabled" });
|
|
3048
|
+
options.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
3049
|
+
return 0;
|
|
3050
|
+
}));
|
|
3051
|
+
}
|
|
3052
|
+
if (parsed.replayFeishuEventPath !== void 0) {
|
|
3053
|
+
const replayFeishuEventPath = parsed.replayFeishuEventPath;
|
|
3054
|
+
return yield* runOneShotDaemon(daemon, () => Effect.gen(function* () {
|
|
3055
|
+
if (!hasFeishuReplayRunner(daemon)) {
|
|
3056
|
+
options.stderr.write(`Bootstrap daemon does not expose replayReceiveMessage(payload)\n`);
|
|
3057
|
+
return 1;
|
|
3058
|
+
}
|
|
3059
|
+
const payload = yield* Effect.tryPromise({
|
|
3060
|
+
try: () => readFeishuReceiveMessagePayload(replayFeishuEventPath),
|
|
3061
|
+
catch: (error) => error
|
|
2379
3062
|
});
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
3063
|
+
const result = yield* daemon.replayReceiveMessage(payload);
|
|
3064
|
+
options.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
3065
|
+
return 0;
|
|
3066
|
+
}));
|
|
3067
|
+
}
|
|
3068
|
+
if (parsed.waitReceive) {
|
|
3069
|
+
const waitReceive = parsed.waitReceive;
|
|
3070
|
+
return yield* runOneShotDaemon(daemon, () => Effect.gen(function* () {
|
|
3071
|
+
if (!hasStatusReporter(daemon)) {
|
|
3072
|
+
options.stderr.write(`Bootstrap daemon does not expose status()\n`);
|
|
3073
|
+
return 1;
|
|
3074
|
+
}
|
|
3075
|
+
yield* Effect.try({
|
|
3076
|
+
try: () => installCliShutdownController(options, daemon),
|
|
3077
|
+
catch: (error) => error
|
|
2392
3078
|
});
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
}
|
|
2401
|
-
installCliShutdownController(options, daemon);
|
|
2402
|
-
await Effect.runPromise(daemon.start());
|
|
2403
|
-
const status = await waitForReceiveStatus(() => Effect.runPromise(daemon.status()), waitReceive, {
|
|
2404
|
-
...parsed.waitReceiveMessageId ? { messageId: parsed.waitReceiveMessageId } : {},
|
|
2405
|
-
...parsed.waitReceiveObservedAfter ? { observedAfter: parsed.waitReceiveObservedAfter } : {},
|
|
2406
|
-
pollMs: parsed.waitPollMs ?? DEFAULT_WAIT_RECEIVE_POLL_MS,
|
|
2407
|
-
...parsed.waitReceiveText ? { text: parsed.waitReceiveText } : {},
|
|
2408
|
-
timeoutMs: parsed.waitTimeoutMs ?? DEFAULT_WAIT_RECEIVE_TIMEOUT_MS
|
|
2409
|
-
});
|
|
2410
|
-
options.stdout.write(`${JSON.stringify(status, null, 2)}\n`);
|
|
2411
|
-
return 0;
|
|
3079
|
+
yield* daemon.start();
|
|
3080
|
+
const status = yield* waitForReceiveStatus(() => daemon.status(), waitReceive, {
|
|
3081
|
+
...parsed.waitReceiveMessageId ? { messageId: parsed.waitReceiveMessageId } : {},
|
|
3082
|
+
...parsed.waitReceiveObservedAfter ? { observedAfter: parsed.waitReceiveObservedAfter } : {},
|
|
3083
|
+
pollMs: parsed.waitPollMs ?? DEFAULT_WAIT_RECEIVE_POLL_MS,
|
|
3084
|
+
...parsed.waitReceiveText ? { text: parsed.waitReceiveText } : {},
|
|
3085
|
+
timeoutMs: parsed.waitTimeoutMs ?? DEFAULT_WAIT_RECEIVE_TIMEOUT_MS
|
|
2412
3086
|
});
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
options.stdout.write("Rivus Agent daemon started\n");
|
|
2417
|
-
return 0;
|
|
2418
|
-
} catch (error) {
|
|
2419
|
-
options.stderr.write(`${formatCliError(error)}\n`);
|
|
2420
|
-
return 1;
|
|
3087
|
+
options.stdout.write(`${JSON.stringify(status, null, 2)}\n`);
|
|
3088
|
+
return 0;
|
|
3089
|
+
}));
|
|
2421
3090
|
}
|
|
2422
|
-
|
|
3091
|
+
yield* Effect.try({
|
|
3092
|
+
try: () => installCliShutdownController(options, daemon),
|
|
3093
|
+
catch: (error) => error
|
|
3094
|
+
});
|
|
3095
|
+
yield* daemon.start();
|
|
3096
|
+
options.stdout.write("Rivus Agent daemon started\n");
|
|
3097
|
+
return 0;
|
|
3098
|
+
}).pipe(Effect.catchAll((error) => Effect.sync(() => {
|
|
3099
|
+
options.stderr.write(`${formatCliError(error)}\n`);
|
|
3100
|
+
return 1;
|
|
3101
|
+
})));
|
|
2423
3102
|
}
|
|
2424
3103
|
async function createCliLegacyDaemon(module, context) {
|
|
2425
3104
|
const factory = module.createRivusDaemonProcess ?? module.default;
|
|
@@ -2430,12 +3109,8 @@ async function createCliDeploymentDaemon(factory, context) {
|
|
|
2430
3109
|
if (!factory) throw new Error("Manifest bootstrap module must export createRivusDeploymentAdapters(context)");
|
|
2431
3110
|
return createRivusDeploymentCliProcess(factory, context);
|
|
2432
3111
|
}
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
return await action();
|
|
2436
|
-
} finally {
|
|
2437
|
-
await Effect.runPromise(daemon.stop());
|
|
2438
|
-
}
|
|
3112
|
+
function runOneShotDaemon(daemon, action) {
|
|
3113
|
+
return Effect.exit(Effect.suspend(action)).pipe(Effect.flatMap((exit) => daemon.stop().pipe(Effect.flatMap(() => exit._tag === "Success" ? Effect.succeed(exit.value) : Effect.failCause(exit.cause)))));
|
|
2439
3114
|
}
|
|
2440
3115
|
function installCliShutdownController(options, daemon) {
|
|
2441
3116
|
createRivusDaemonShutdownController({
|
|
@@ -3258,23 +3933,22 @@ async function fetchLiveStatus(url) {
|
|
|
3258
3933
|
if (!response.ok) throw new Error(`Status request failed with HTTP ${response.status}`);
|
|
3259
3934
|
return response.json();
|
|
3260
3935
|
}
|
|
3261
|
-
|
|
3262
|
-
return
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
}))
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
}
|
|
3936
|
+
function waitForReceiveStatus(readStatus, target, options) {
|
|
3937
|
+
return Effect.suspend(() => {
|
|
3938
|
+
const startedAt = Date.now();
|
|
3939
|
+
let lastStatus;
|
|
3940
|
+
const poll = () => readStatus().pipe(Effect.flatMap((status) => {
|
|
3941
|
+
lastStatus = status;
|
|
3942
|
+
if (hasReceiveObservation(status, target, {
|
|
3943
|
+
...options.messageId ? { messageId: options.messageId } : {},
|
|
3944
|
+
...options.observedAfter ? { observedAfter: options.observedAfter } : {},
|
|
3945
|
+
...options.text ? { text: options.text } : {}
|
|
3946
|
+
})) return Effect.succeed(status);
|
|
3947
|
+
if (Date.now() - startedAt >= options.timeoutMs) return Effect.fail(new ReceiveWaitTimeoutError(target, lastStatus, options.messageId, options.observedAfter, options.text));
|
|
3948
|
+
return Effect.sleep(options.pollMs).pipe(Effect.flatMap(poll));
|
|
3949
|
+
}));
|
|
3950
|
+
return poll();
|
|
3951
|
+
});
|
|
3278
3952
|
}
|
|
3279
3953
|
function hasReceiveObservation(status, target, criteria) {
|
|
3280
3954
|
const receive = readRecord(status)?.receive;
|
|
@@ -3311,9 +3985,6 @@ function readObservedAtMs(value) {
|
|
|
3311
3985
|
return Number.isNaN(time) ? void 0 : time;
|
|
3312
3986
|
}
|
|
3313
3987
|
}
|
|
3314
|
-
function sleep(ms) {
|
|
3315
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3316
|
-
}
|
|
3317
3988
|
function parseWaitReceive(value) {
|
|
3318
3989
|
return value === "accepted" || value === "handled" ? value : void 0;
|
|
3319
3990
|
}
|
|
@@ -3338,4 +4009,4 @@ function hasRecoveryRunner(daemon) {
|
|
|
3338
4009
|
return typeof daemon.openRecoveryControl === "function";
|
|
3339
4010
|
}
|
|
3340
4011
|
//#endregion
|
|
3341
|
-
export {
|
|
4012
|
+
export { validateCardPresentationLeaseMs as $, RivusDeploymentReadinessError as A, FeishuEndpointCredentialError as B, AgentInstanceConflict as C, RivusDeploymentAutomationReadinessError as D, RivusPluginLoadError as E, WorkspaceInstructionsSourceError as F, toEffectAgentRuntime as G, RivusDaemonConfigError as H, createRivusDaemonShutdownController as I, createRuntimeCache as J, toEffectAgentRuntimeInput as K, OpenClawEnvImportError as L, InvalidRivusProjectSpace as M, createStableId as N, RivusDeploymentBackgroundSessionReadinessError as O, InvalidWorkspaceRoot as P, cardPresentationLeaseDeadline as Q, createRivusEnvFromOpenClawConfig as R, InvalidAgentHostBinding as S, loadRivusDeploymentManifest as T, loadRivusDaemonConfig as U, resolveFeishuEndpointCredentials as V, loadMergedLocalEnvFile as W, invokeRuntimeControl as X, disposeRuntimeCacheEntries as Y, DEFAULT_CARD_STREAM_LEASE_MS$1 as Z, createAgentHostFromRuntimePort as _, createWorkspaceRootHandle as a, createCardPresentationService as at, AgentInstanceBusy as b, createNodeRivusPluginModuleLoader as c, activeCardPresentation as ct, validateTrustedModulePath as d, validateCardPresentationChain as dt, markCardDeliveryTerminal as et, isPathWithin as f, DEFAULT_CONVERSATION_PROGRESS_DISPLAY as ft, toEffectProcessAgentRuntimeInput as g, createProcessDeploymentControlPorts as h, resolveRivusProjectSpace as i, CardPresentationNotFound as it, validateRivusDeploymentManifest as j, RivusDeploymentDaemonLifecycleError as k, loadNodeRivusPluginModule as l, createCardPresentationChain as lt, resolveRivusPluginModule as m, createRivusDeploymentCliProcess as n, validateCardDeliveryRecord as nt, createRivusDeploymentControlModule as o, CardPresentationTransitionDenied as ot, findTrustedPackageRoot as p, toProcessAgentRuntimeInput as q, createAgentsMdInstructionsResolver as r, validateCardDeliveryTransition as rt, resolveRivusDeploymentModule as s, acceptsCardPresentationProgress as st, runRivusDaemonCli as t, reserveCardDeliverySequence as tt, resolveNodeRivusPluginModulePath as u, isCardPresentationHandoffDue as ut, createAgentHostRuntimePort as v, RivusDeploymentManifestError as w, AgentRuntimeDisposed as x, createAgentInstanceRegistryModule as y, formatRivusEnvFile as z };
|