@rivus/agent 0.14.2 → 0.14.4
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 +1 -1
- package/dist/acp.js +1 -2
- package/dist/bootstrap/pi-feishu.d.ts +1 -1
- package/dist/bootstrap/pi-feishu.js +4 -4
- package/dist/chunks/agent-loop.d.ts +55 -300
- package/dist/chunks/agent-loop.js +3 -1123
- package/dist/chunks/background-session-authority.js +230 -0
- package/dist/chunks/background-session-control-input.js +51 -0
- package/dist/chunks/background-session-service.d.ts +382 -0
- package/dist/chunks/index.d.ts +1201 -538
- package/dist/chunks/pi-tool-proxy.d.ts +22 -90
- package/dist/chunks/pi.js +5 -2
- package/dist/chunks/rivus-agent-definition-resolver.js +508 -0
- package/dist/chunks/rivus-daemon-cli.js +2776 -3244
- package/dist/chunks/rivus-plugin-testkit.d.ts +175 -2
- package/dist/chunks/rivus-plugin-testkit.js +11 -4
- package/dist/chunks/rivus-skill.d.ts +95 -0
- package/dist/chunks/sha256-digest.js +2 -7
- package/dist/chunks/src.js +11941 -7001
- package/dist/chunks/tool-input-digest.js +158 -0
- package/dist/cli.js +764 -712
- package/dist/index.d.ts +6 -7
- package/dist/index.js +7 -8
- package/dist/mcp.d.ts +48 -9
- package/dist/mcp.js +146 -20
- package/dist/pi.d.ts +3 -4
- package/dist/pi.js +1 -1
- package/package.json +5 -5
- package/dist/chunks/api.d.ts +0 -70
- package/dist/chunks/api.js +0 -471
- package/dist/chunks/api2.d.ts +0 -387
- package/dist/chunks/api2.js +0 -1331
- package/dist/chunks/api3.d.ts +0 -402
- package/dist/chunks/module.js +0 -267
- package/dist/chunks/pi-skill-tool.js +0 -460
- package/dist/chunks/spi.d.ts +0 -1
- package/dist/chunks/spi.js +0 -2
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { B as loadMergedLocalEnvFile, L as resolveFeishuEndpointCredentials, _ as findTrustedPackageRoot, g as isPathWithin, h as validateTrustedModulePath, j as validateRivusDeploymentManifest, k as loadRivusDeploymentManifest, m as resolveNodeRivusPluginModulePath, t as runRivusDaemonCli } from "./chunks/rivus-daemon-cli.js";
|
|
3
3
|
import { Effect, Either } from "effect";
|
|
4
4
|
import { lstat, mkdir, readFile, realpath, rmdir, stat, unlink, writeFile } from "node:fs/promises";
|
|
5
5
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
@@ -33,7 +33,7 @@ function checkManifest$1(home) {
|
|
|
33
33
|
manifest
|
|
34
34
|
};
|
|
35
35
|
}), Effect.catchAll((error) => Effect.succeed({ check: {
|
|
36
|
-
message: `deployment manifest is invalid: ${formatError
|
|
36
|
+
message: `deployment manifest is invalid: ${formatError(error)}`,
|
|
37
37
|
name: "manifest",
|
|
38
38
|
status: "fail"
|
|
39
39
|
} })));
|
|
@@ -49,7 +49,7 @@ function checkModules(home, manifest, resolveModule, resolvePluginModule) {
|
|
|
49
49
|
const missing = [];
|
|
50
50
|
for (const specifier of specifiers) {
|
|
51
51
|
const result = yield* toEffect(() => resolveModule(specifier)).pipe(Effect.either);
|
|
52
|
-
if (result._tag === "Left") missing.push(`${specifier} (${formatError
|
|
52
|
+
if (result._tag === "Left") missing.push(`${specifier} (${formatError(result.left)})`);
|
|
53
53
|
}
|
|
54
54
|
for (const plugin of manifest.plugins) {
|
|
55
55
|
const result = yield* toEffect(() => resolvePluginModule({
|
|
@@ -57,7 +57,7 @@ function checkModules(home, manifest, resolveModule, resolvePluginModule) {
|
|
|
57
57
|
module: plugin.module,
|
|
58
58
|
pluginId: plugin.id
|
|
59
59
|
})).pipe(Effect.either);
|
|
60
|
-
if (result._tag === "Left") missing.push(`${plugin.module} (${formatError
|
|
60
|
+
if (result._tag === "Left") missing.push(`${plugin.module} (${formatError(result.left)})`);
|
|
61
61
|
}
|
|
62
62
|
return missing.length === 0 ? {
|
|
63
63
|
message: "Bootstrap, Plugin, Pi, and Feishu modules resolve from the global installation",
|
|
@@ -102,7 +102,7 @@ function checkCredentials$1(home, manifest, env, envFilePath) {
|
|
|
102
102
|
},
|
|
103
103
|
catch: toError$1
|
|
104
104
|
}).pipe(Effect.catchAll((error) => Effect.succeed({
|
|
105
|
-
message: `enabled Endpoint credentials are incomplete: ${formatError
|
|
105
|
+
message: `enabled Endpoint credentials are incomplete: ${formatError(error)}`,
|
|
106
106
|
name: "credentials",
|
|
107
107
|
status: "fail"
|
|
108
108
|
})));
|
|
@@ -120,404 +120,333 @@ function defaultResolveModule(specifier, packageManifestPath) {
|
|
|
120
120
|
function toError$1(error) {
|
|
121
121
|
return error instanceof Error ? error : new Error(String(error));
|
|
122
122
|
}
|
|
123
|
-
function formatError
|
|
123
|
+
function formatError(error) {
|
|
124
124
|
return error instanceof Error ? error.message : String(error);
|
|
125
125
|
}
|
|
126
126
|
//#endregion
|
|
127
|
-
//#region src/
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
"
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
127
|
+
//#region src/platform/home/config/rivus-home-config.ts
|
|
128
|
+
/** Loads and validates the operator-owned Home config file. */
|
|
129
|
+
function loadRivusHome(directoryPath) {
|
|
130
|
+
const directory = resolve(directoryPath);
|
|
131
|
+
const configPath = resolve(directory, "config.json");
|
|
132
|
+
return Effect.tryPromise({
|
|
133
|
+
try: async () => {
|
|
134
|
+
const config = parseRivusHomeConfig(JSON.parse(await readFile(configPath, "utf8")));
|
|
135
|
+
return {
|
|
136
|
+
bootstrap: resolveBootstrap(directory, config.bootstrap),
|
|
137
|
+
config,
|
|
138
|
+
configPath,
|
|
139
|
+
directory,
|
|
140
|
+
envFilePath: resolveHomePath(directory, config.envFile, "envFile"),
|
|
141
|
+
logsDirectory: resolveHomePath(directory, config.logs, "logs"),
|
|
142
|
+
manifestPath: resolveHomePath(directory, config.manifest, "manifest"),
|
|
143
|
+
stateDirectory: resolveHomePath(directory, config.state, "state"),
|
|
144
|
+
workspaceDirectory: resolveHomePath(directory, config.workspace, "workspace")
|
|
145
|
+
};
|
|
146
|
+
},
|
|
147
|
+
catch: asError$1
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
function parseRivusHomeConfig(value) {
|
|
151
|
+
const config = record(value, "Rivus Home config");
|
|
152
|
+
if (config.version !== 1) throw new Error("Rivus Home config version must be 1");
|
|
153
|
+
return {
|
|
154
|
+
bootstrap: nonEmptyString(config.bootstrap, "bootstrap"),
|
|
155
|
+
envFile: relativePath(config.envFile, "envFile"),
|
|
156
|
+
logs: relativePath(config.logs, "logs"),
|
|
157
|
+
manifest: relativePath(config.manifest, "manifest"),
|
|
158
|
+
state: relativePath(config.state, "state"),
|
|
159
|
+
version: 1,
|
|
160
|
+
workspace: relativePath(config.workspace, "workspace")
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function resolveBootstrap(directory, value) {
|
|
164
|
+
if (value.startsWith("./") || value.startsWith("../")) return resolveHomePath(directory, value, "bootstrap");
|
|
165
|
+
if (isAbsolute(value)) throw new Error("Rivus Home bootstrap must be a package specifier or a relative path inside Rivus Home");
|
|
166
|
+
return value;
|
|
167
|
+
}
|
|
168
|
+
function resolveHomePath(directory, value, owner) {
|
|
169
|
+
const candidate = resolve(directory, value);
|
|
170
|
+
const relation = relative(directory, candidate);
|
|
171
|
+
if (relation === "" || !relation.startsWith(`..${sep}`) && relation !== ".." && !isAbsolute(relation)) return candidate;
|
|
172
|
+
throw new Error(`Rivus Home ${owner} escapes the Home directory`);
|
|
173
|
+
}
|
|
174
|
+
function relativePath(value, owner) {
|
|
175
|
+
const path = nonEmptyString(value, owner);
|
|
176
|
+
if (isAbsolute(path)) throw new Error(`Rivus Home ${owner} must be a relative path`);
|
|
177
|
+
return path;
|
|
178
|
+
}
|
|
179
|
+
function nonEmptyString(value, owner) {
|
|
180
|
+
if (typeof value !== "string" || !value.trim()) throw new Error(`Rivus Home ${owner} must be a non-empty string`);
|
|
181
|
+
return value;
|
|
182
|
+
}
|
|
183
|
+
function record(value, owner) {
|
|
184
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${owner} must be an object`);
|
|
185
|
+
return value;
|
|
186
|
+
}
|
|
187
|
+
function asError$1(error) {
|
|
188
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
189
|
+
}
|
|
190
|
+
//#endregion
|
|
191
|
+
//#region src/platform/home/doctor/rivus-home-doctor.ts
|
|
192
|
+
function diagnoseRivusHome(input, options) {
|
|
139
193
|
return Effect.gen(function* () {
|
|
140
|
-
const
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
const
|
|
151
|
-
checks.push(
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
catch: toError
|
|
156
|
-
}));
|
|
157
|
-
return Object.freeze({
|
|
158
|
-
checks: Object.freeze(checks),
|
|
159
|
-
directory,
|
|
160
|
-
ready: checks.every(({ status }) => status === "pass")
|
|
194
|
+
const checks = [checkNode$1(input.nodeVersion)];
|
|
195
|
+
const loaded = yield* options.load(input.directory).pipe(Effect.either);
|
|
196
|
+
if (Either.isLeft(loaded)) {
|
|
197
|
+
checks.push({
|
|
198
|
+
message: `Rivus Home is invalid: ${loaded.left.message}`,
|
|
199
|
+
name: "home",
|
|
200
|
+
status: "fail"
|
|
201
|
+
}, blockedCheck("workspace", "Workspace"), blockedCheck("manifest", "manifest"), blockedCheck("modules", "modules"), blockedCheck("credentials", "credentials"));
|
|
202
|
+
return report(input.directory, checks);
|
|
203
|
+
}
|
|
204
|
+
const home = loaded.right;
|
|
205
|
+
checks.push({
|
|
206
|
+
message: "config.json is valid and contained in Rivus Home",
|
|
207
|
+
name: "home",
|
|
208
|
+
status: "pass"
|
|
161
209
|
});
|
|
210
|
+
checks.push(yield* checkWorkspace(home, options.findMissingWorkspacePaths));
|
|
211
|
+
const deployment = yield* options.deploymentInspector.inspect({
|
|
212
|
+
env: input.env,
|
|
213
|
+
...input.envFilePath ? { envFilePath: input.envFilePath } : {},
|
|
214
|
+
home
|
|
215
|
+
});
|
|
216
|
+
checks.push(deployment.manifest, deployment.modules, deployment.credentials);
|
|
217
|
+
return report(input.directory, checks);
|
|
162
218
|
});
|
|
163
219
|
}
|
|
164
220
|
function checkNode$1(version) {
|
|
165
221
|
const [major, minor] = version.split(".").map(Number);
|
|
166
|
-
|
|
222
|
+
return major === 24 && Number.isInteger(minor) && minor >= 11 ? {
|
|
167
223
|
message: `Node.js ${version} satisfies the supported ^24.11 runtime`,
|
|
168
224
|
name: "node",
|
|
169
225
|
status: "pass"
|
|
170
|
-
}
|
|
171
|
-
return {
|
|
226
|
+
} : {
|
|
172
227
|
message: `Node.js 24.11 or newer on major 24 is required; current version is ${version}`,
|
|
173
228
|
name: "node",
|
|
174
229
|
status: "fail"
|
|
175
230
|
};
|
|
176
231
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
name: "files",
|
|
232
|
+
function checkWorkspace(home, findMissingWorkspacePaths) {
|
|
233
|
+
return findMissingWorkspacePaths(home).pipe(Effect.map((missing) => missing.length === 0 ? {
|
|
234
|
+
message: "Workspace instructions, Memory, Skills, and work directories are present",
|
|
235
|
+
name: "workspace",
|
|
182
236
|
status: "pass"
|
|
183
237
|
} : {
|
|
184
|
-
message: `
|
|
185
|
-
name: "
|
|
238
|
+
message: `Workspace paths are missing: ${missing.join(", ")}`,
|
|
239
|
+
name: "workspace",
|
|
186
240
|
status: "fail"
|
|
187
|
-
};
|
|
241
|
+
}));
|
|
188
242
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
return missing.length === 0 ? {
|
|
194
|
-
message: "Rivus, Pi, and Feishu dependencies are installed",
|
|
195
|
-
name: "dependencies",
|
|
196
|
-
status: "pass"
|
|
197
|
-
} : {
|
|
198
|
-
message: `run npm install; missing local dependencies: ${missing.join(", ")}`,
|
|
199
|
-
name: "dependencies",
|
|
243
|
+
function blockedCheck(name, owner) {
|
|
244
|
+
return {
|
|
245
|
+
message: `${owner} cannot be checked until config.json is valid`,
|
|
246
|
+
name,
|
|
200
247
|
status: "fail"
|
|
201
248
|
};
|
|
202
249
|
}
|
|
203
|
-
function
|
|
204
|
-
return
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
const result = yield* resolveNodeRivusPluginModulePath({
|
|
209
|
-
deploymentRoot: directory,
|
|
210
|
-
module: plugin.module,
|
|
211
|
-
pluginId: plugin.id
|
|
212
|
-
}).pipe(Effect.either);
|
|
213
|
-
if (Either.isLeft(result)) missingModules.push(plugin.module);
|
|
214
|
-
}
|
|
215
|
-
if (missingModules.length > 0) return { check: {
|
|
216
|
-
message: `manifest Plugin modules are missing: ${missingModules.join(", ")}`,
|
|
217
|
-
name: "manifest",
|
|
218
|
-
status: "fail"
|
|
219
|
-
} };
|
|
220
|
-
return {
|
|
221
|
-
check: {
|
|
222
|
-
message: "deployment manifest is valid",
|
|
223
|
-
name: "manifest",
|
|
224
|
-
status: "pass"
|
|
225
|
-
},
|
|
226
|
-
manifest
|
|
227
|
-
};
|
|
228
|
-
})), Effect.catchAll((error) => Effect.succeed({ check: {
|
|
229
|
-
message: `deployment manifest is invalid: ${error.message}`,
|
|
230
|
-
name: "manifest",
|
|
231
|
-
status: "fail"
|
|
232
|
-
} })));
|
|
233
|
-
}
|
|
234
|
-
async function checkCredentials(manifest, envFilePath, env) {
|
|
235
|
-
let mergedEnv;
|
|
236
|
-
try {
|
|
237
|
-
mergedEnv = await loadMergedLocalEnvFile(envFilePath, env);
|
|
238
|
-
} catch (error) {
|
|
239
|
-
return {
|
|
240
|
-
message: `required env file is unavailable at ${envFilePath}: ${error instanceof Error ? error.message : String(error)}`,
|
|
241
|
-
name: "credentials",
|
|
242
|
-
status: "fail"
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
if (!manifest) return {
|
|
246
|
-
message: "enabled Endpoint credentials cannot be checked until the deployment manifest is valid",
|
|
247
|
-
name: "credentials",
|
|
248
|
-
status: "fail"
|
|
250
|
+
function report(directory, checks) {
|
|
251
|
+
return {
|
|
252
|
+
checks,
|
|
253
|
+
directory,
|
|
254
|
+
ready: checks.every(({ status }) => status === "pass")
|
|
249
255
|
};
|
|
250
|
-
try {
|
|
251
|
-
for (const endpoint of manifest.endpoints.filter(({ enabled }) => enabled)) resolveFeishuEndpointCredentials(endpoint.credentialRef, mergedEnv);
|
|
252
|
-
return {
|
|
253
|
-
message: "enabled Endpoint credential references resolve",
|
|
254
|
-
name: "credentials",
|
|
255
|
-
status: "pass"
|
|
256
|
-
};
|
|
257
|
-
} catch (error) {
|
|
258
|
-
return {
|
|
259
|
-
message: `enabled Endpoint credentials are incomplete: ${error instanceof Error ? error.message : String(error)}`,
|
|
260
|
-
name: "credentials",
|
|
261
|
-
status: "fail"
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
async function missingRegularFiles(directory, paths) {
|
|
266
|
-
const missing = [];
|
|
267
|
-
for (const path of paths) try {
|
|
268
|
-
if (!(await stat(resolve(directory, path))).isFile()) missing.push(path);
|
|
269
|
-
} catch (error) {
|
|
270
|
-
if (!isMissingPath$1(error)) throw error;
|
|
271
|
-
missing.push(path);
|
|
272
|
-
}
|
|
273
|
-
return missing;
|
|
274
|
-
}
|
|
275
|
-
function isMissingPath$1(error) {
|
|
276
|
-
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
277
|
-
}
|
|
278
|
-
function toError(error) {
|
|
279
|
-
return error instanceof Error ? error : new Error(String(error));
|
|
280
256
|
}
|
|
281
257
|
//#endregion
|
|
282
|
-
//#region src/platform/
|
|
283
|
-
const
|
|
284
|
-
"
|
|
285
|
-
"
|
|
286
|
-
"
|
|
287
|
-
"
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
["deploy/launchd/com.rivus.agent.plist", launchdService(directory, options.nodeExecutable)],
|
|
296
|
-
["deploy/systemd/rivus.service", systemdService(directory, options.nodeExecutable)],
|
|
297
|
-
["package.json", projectPackageJson(directory, manifest)],
|
|
298
|
-
["rivus.config.json", deploymentManifest$1()]
|
|
299
|
-
]);
|
|
300
|
-
for (const [target, source] of Object.entries(TEMPLATE_FILES)) files.set(target, await readFile(join(options.templateDirectory, source), "utf8"));
|
|
301
|
-
const paths = [...files.keys()].sort();
|
|
302
|
-
await assertSafeProjectAncestors(directory, paths);
|
|
303
|
-
const conflicts = await findConflicts(directory, paths);
|
|
304
|
-
if (conflicts.length > 0) throw new Error(`Rivus project initialization refused; refusing to overwrite: ${conflicts.join(", ")}`);
|
|
305
|
-
const createdDirectories = [];
|
|
306
|
-
const createdFiles = [];
|
|
307
|
-
const writeProjectFile = options.writeProjectFile ?? writeExclusiveProjectFile;
|
|
308
|
-
try {
|
|
309
|
-
for (const path of paths) {
|
|
310
|
-
const destination = join(directory, path);
|
|
311
|
-
await ensureDirectory(dirname(destination), directory, createdDirectories);
|
|
312
|
-
await assertSafeProjectAncestors(directory, [path]);
|
|
313
|
-
await writeProjectFile(destination, files.get(path));
|
|
314
|
-
createdFiles.push(destination);
|
|
315
|
-
}
|
|
316
|
-
} catch (error) {
|
|
317
|
-
const rollbackErrors = await rollbackCreatedPaths(createdFiles, createdDirectories);
|
|
318
|
-
if (rollbackErrors.length > 0) throw new AggregateError([error, ...rollbackErrors], "Rivus project initialization failed and rollback was incomplete");
|
|
319
|
-
throw error;
|
|
320
|
-
}
|
|
258
|
+
//#region src/platform/home/setup/rivus-home-layout.ts
|
|
259
|
+
const HOME_DIRECTORIES = Object.freeze([
|
|
260
|
+
"logs",
|
|
261
|
+
"plugins",
|
|
262
|
+
"state",
|
|
263
|
+
"workspace/memory",
|
|
264
|
+
"workspace/skills",
|
|
265
|
+
"workspace/work/artifacts",
|
|
266
|
+
"workspace/work/drafts",
|
|
267
|
+
"workspace/work/inbox",
|
|
268
|
+
"workspace/work/tmp"
|
|
269
|
+
]);
|
|
270
|
+
function createRivusHomeLayout() {
|
|
321
271
|
return {
|
|
322
|
-
|
|
323
|
-
files:
|
|
272
|
+
directories: HOME_DIRECTORIES,
|
|
273
|
+
files: /* @__PURE__ */ new Map([
|
|
274
|
+
[".env.example", environmentTemplate$1()],
|
|
275
|
+
[".gitignore", ".DS_Store\n.env\nlogs/\nstate/\nworkspace/work/tmp/\n"],
|
|
276
|
+
["config.json", homeConfig()],
|
|
277
|
+
["rivus.config.json", deploymentManifest$1()],
|
|
278
|
+
["workspace/AGENTS.md", agentsTemplate()],
|
|
279
|
+
["workspace/IDENTITY.md", "# Identity\n\nName: Rivus\n\nRole: Personal agent\n"],
|
|
280
|
+
["workspace/MEMORY.md", "# Curated Memory\n\nStore confirmed, durable facts and decisions here.\n"],
|
|
281
|
+
["workspace/SOUL.md", "# Character\n\nBe direct, thoughtful, and evidence-led.\n"],
|
|
282
|
+
["workspace/USER.md", "# User\n\nRecord stable user preferences here.\n"],
|
|
283
|
+
["plugins/.gitkeep", ""],
|
|
284
|
+
["workspace/memory/.gitkeep", ""],
|
|
285
|
+
["workspace/skills/.gitkeep", ""],
|
|
286
|
+
["workspace/work/artifacts/.gitkeep", ""],
|
|
287
|
+
["workspace/work/drafts/.gitkeep", ""],
|
|
288
|
+
["workspace/work/inbox/.gitkeep", ""]
|
|
289
|
+
])
|
|
324
290
|
};
|
|
325
291
|
}
|
|
326
|
-
|
|
327
|
-
await writeFile(path, contents, {
|
|
328
|
-
encoding: "utf8",
|
|
329
|
-
flag: "wx"
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
async function assertSafeProjectAncestors(directory, paths) {
|
|
333
|
-
const rootState = await lstatOrUndefined(directory);
|
|
334
|
-
if (rootState?.isSymbolicLink()) throw new Error("Rivus project initialization refused; symbolic link ancestor: .");
|
|
335
|
-
if (rootState && !rootState.isDirectory()) throw new Error("Rivus project initialization refused; project path is not a directory");
|
|
336
|
-
if (!rootState) return;
|
|
337
|
-
for (const path of paths) {
|
|
338
|
-
let current = directory;
|
|
339
|
-
for (const segment of path.split("/").slice(0, -1)) {
|
|
340
|
-
current = join(current, segment);
|
|
341
|
-
const state = await lstatOrUndefined(current);
|
|
342
|
-
if (!state) break;
|
|
343
|
-
if (state.isSymbolicLink()) throw new Error(`Rivus project initialization refused; symbolic link ancestor: ${relative(directory, current)}`);
|
|
344
|
-
if (!state.isDirectory()) throw new Error(`Rivus project initialization refused; non-directory ancestor: ${relative(directory, current)}`);
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
async function ensureDirectory(path, projectRoot, createdDirectories) {
|
|
349
|
-
try {
|
|
350
|
-
await mkdir(path);
|
|
351
|
-
createdDirectories.push(path);
|
|
352
|
-
} catch (error) {
|
|
353
|
-
if (isMissingPath(error)) {
|
|
354
|
-
const parent = dirname(path);
|
|
355
|
-
if (parent === path) throw error;
|
|
356
|
-
await ensureDirectory(parent, projectRoot, createdDirectories);
|
|
357
|
-
await ensureDirectory(path, projectRoot, createdDirectories);
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
if (!isAlreadyExists(error)) throw error;
|
|
361
|
-
const state = await lstat(path);
|
|
362
|
-
if (state.isSymbolicLink()) {
|
|
363
|
-
if (isPathWithin(projectRoot, path)) throw new Error(`Rivus project initialization refused; symbolic link ancestor: ${relative(projectRoot, path) || "."}`);
|
|
364
|
-
if ((await stat(path)).isDirectory()) return;
|
|
365
|
-
}
|
|
366
|
-
if (!state.isDirectory()) throw new Error(`Rivus project initialization refused; non-directory ancestor: ${path}`);
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
async function rollbackCreatedPaths(files, directories) {
|
|
370
|
-
const errors = [];
|
|
371
|
-
for (const path of files.reverse()) try {
|
|
372
|
-
await unlink(path);
|
|
373
|
-
} catch (error) {
|
|
374
|
-
if (!isMissingPath(error)) errors.push(asError$1(error));
|
|
375
|
-
}
|
|
376
|
-
for (const path of directories.reverse()) try {
|
|
377
|
-
await rmdir(path);
|
|
378
|
-
} catch (error) {
|
|
379
|
-
if (!isMissingPath(error) && !isDirectoryNotEmpty(error)) errors.push(asError$1(error));
|
|
380
|
-
}
|
|
381
|
-
return errors;
|
|
382
|
-
}
|
|
383
|
-
async function lstatOrUndefined(path) {
|
|
384
|
-
try {
|
|
385
|
-
return await lstat(path);
|
|
386
|
-
} catch (error) {
|
|
387
|
-
if (isMissingPath(error)) return void 0;
|
|
388
|
-
throw error;
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
function isAlreadyExists(error) {
|
|
392
|
-
return error instanceof Error && "code" in error && error.code === "EEXIST";
|
|
393
|
-
}
|
|
394
|
-
function isDirectoryNotEmpty(error) {
|
|
395
|
-
return error instanceof Error && "code" in error && error.code === "ENOTEMPTY";
|
|
396
|
-
}
|
|
397
|
-
function asError$1(error) {
|
|
398
|
-
return error instanceof Error ? error : new Error(String(error));
|
|
399
|
-
}
|
|
400
|
-
async function readPackageManifest(path) {
|
|
401
|
-
const manifest = JSON.parse(await readFile(path, "utf8"));
|
|
402
|
-
if (manifest.name !== "@rivus/agent" || typeof manifest.version !== "string") throw new Error("Rivus package manifest is missing its release identity");
|
|
403
|
-
return manifest;
|
|
404
|
-
}
|
|
405
|
-
async function findConflicts(directory, paths) {
|
|
406
|
-
const conflicts = [];
|
|
407
|
-
for (const path of paths) try {
|
|
408
|
-
await lstat(join(directory, path));
|
|
409
|
-
conflicts.push(path);
|
|
410
|
-
} catch (error) {
|
|
411
|
-
if (!isMissingPath(error)) throw error;
|
|
412
|
-
}
|
|
413
|
-
return conflicts;
|
|
414
|
-
}
|
|
415
|
-
function isMissingPath(error) {
|
|
416
|
-
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
417
|
-
}
|
|
418
|
-
function projectPackageJson(directory, manifest) {
|
|
419
|
-
const pi = manifest.dependencies?.["@earendil-works/pi-coding-agent"];
|
|
420
|
-
const lark = manifest.dependencies?.["@larksuiteoapi/node-sdk"];
|
|
421
|
-
const effect = manifest.dependencies?.effect;
|
|
422
|
-
if (!pi || !lark || !effect) throw new Error("Rivus package manifest is missing its Effect, Pi, or Feishu dependency range");
|
|
423
|
-
const projectName = sanitizePackageName(basename(directory));
|
|
292
|
+
function homeConfig() {
|
|
424
293
|
return `${JSON.stringify({
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
},
|
|
433
|
-
dependencies: {
|
|
434
|
-
"@earendil-works/pi-coding-agent": pi,
|
|
435
|
-
"@larksuiteoapi/node-sdk": lark,
|
|
436
|
-
"@rivus/agent": `^${manifest.version}`,
|
|
437
|
-
effect
|
|
438
|
-
}
|
|
294
|
+
bootstrap: "@rivus/agent/bootstrap/pi-feishu",
|
|
295
|
+
envFile: ".env",
|
|
296
|
+
logs: "logs",
|
|
297
|
+
manifest: "rivus.config.json",
|
|
298
|
+
state: "state",
|
|
299
|
+
version: 1,
|
|
300
|
+
workspace: "workspace"
|
|
439
301
|
}, null, 2)}\n`;
|
|
440
302
|
}
|
|
441
|
-
function sanitizePackageName(value) {
|
|
442
|
-
return value.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[._-]+|[._-]+$/g, "") || "rivus-app";
|
|
443
|
-
}
|
|
444
303
|
function deploymentManifest$1() {
|
|
445
304
|
return `${JSON.stringify({
|
|
446
|
-
plugins: [{
|
|
447
|
-
id: "rivus-starter",
|
|
448
|
-
module: "./rivus-agents.plugin.mjs",
|
|
449
|
-
required: true
|
|
450
|
-
}],
|
|
451
305
|
agents: [{
|
|
452
|
-
agentId: "
|
|
453
|
-
endpointIds: ["feishu
|
|
306
|
+
agentId: "personal",
|
|
307
|
+
endpointIds: ["personal-feishu"],
|
|
308
|
+
memory: {
|
|
309
|
+
scopes: ["agent-private"],
|
|
310
|
+
tool: true
|
|
311
|
+
},
|
|
454
312
|
pluginId: "rivus-starter",
|
|
455
313
|
profileId: "agent-a",
|
|
314
|
+
projectSpaceId: "personal-home",
|
|
315
|
+
runtimeTools: { allow: [
|
|
316
|
+
"read",
|
|
317
|
+
"bash",
|
|
318
|
+
"edit",
|
|
319
|
+
"write",
|
|
320
|
+
"grep",
|
|
321
|
+
"find",
|
|
322
|
+
"ls"
|
|
323
|
+
] },
|
|
456
324
|
skills: { allow: [] },
|
|
457
325
|
tools: { allow: ["rivus-starter/current-weather"] }
|
|
458
326
|
}],
|
|
459
|
-
defaultAgentId: "
|
|
460
|
-
defaultEndpointId: "feishu
|
|
327
|
+
defaultAgentId: "personal",
|
|
328
|
+
defaultEndpointId: "personal-feishu",
|
|
461
329
|
endpoints: [{
|
|
462
|
-
agentId: "
|
|
330
|
+
agentId: "personal",
|
|
463
331
|
baseUrl: "https://open.feishu.cn",
|
|
464
332
|
cardStreamLeaseMs: 51e4,
|
|
465
333
|
credentialRef: "env:RIVUS_FEISHU",
|
|
466
334
|
enabled: true,
|
|
467
335
|
experimental: { cotMessages: false },
|
|
468
336
|
groupPolicy: "mention-only",
|
|
469
|
-
id: "feishu
|
|
337
|
+
id: "personal-feishu",
|
|
470
338
|
progressDisplay: "collapsed",
|
|
471
339
|
required: true,
|
|
472
|
-
sessionNamespace: "rivus-
|
|
340
|
+
sessionNamespace: "rivus-home-v1",
|
|
473
341
|
streamMinIntervalMs: 200
|
|
342
|
+
}],
|
|
343
|
+
plugins: [{
|
|
344
|
+
id: "rivus-starter",
|
|
345
|
+
module: "@rivus/agent/plugin/starter",
|
|
346
|
+
required: true
|
|
347
|
+
}],
|
|
348
|
+
projectSpaces: [{
|
|
349
|
+
id: "personal-home",
|
|
350
|
+
root: "workspace",
|
|
351
|
+
skills: { sources: ["skills"] },
|
|
352
|
+
workingDirectory: "."
|
|
474
353
|
}]
|
|
475
354
|
}, null, 2)}\n`;
|
|
476
355
|
}
|
|
477
356
|
function environmentTemplate$1() {
|
|
478
357
|
return [
|
|
479
|
-
"# Copy this file to .env
|
|
358
|
+
"# Copy this file to .env and keep the real values untracked.",
|
|
480
359
|
"RIVUS_FEISHU_APP_ID=",
|
|
481
360
|
"RIVUS_FEISHU_APP_SECRET=",
|
|
482
361
|
"PI_MODEL=",
|
|
483
362
|
"PI_API_KEY=",
|
|
484
363
|
"# PI_BASE_URL=",
|
|
485
364
|
"RIVUS_WEATHER_DEFAULT_LOCATION=上海",
|
|
486
|
-
"# LANGFUSE_BASE_URL=https://jp.cloud.langfuse.com",
|
|
487
|
-
"# LANGFUSE_PUBLIC_KEY=",
|
|
488
|
-
"# LANGFUSE_SECRET_KEY=",
|
|
489
|
-
"# RIVUS_TELEMETRY_CONTENT=redacted",
|
|
490
365
|
""
|
|
491
366
|
].join("\n");
|
|
492
367
|
}
|
|
493
|
-
function
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
".env.local",
|
|
509
|
-
"--bootstrap",
|
|
510
|
-
"./rivus.bootstrap.ts",
|
|
511
|
-
"--manifest",
|
|
512
|
-
"./rivus.config.json"
|
|
513
|
-
].map((value) => ` <string>${xmlEscape(value)}</string>`).join("\n")}\n </array>\n <key>WorkingDirectory</key>\n <string>${xmlEscape(directory)}</string>\n <key>RunAtLoad</key>\n <true/>\n <key>KeepAlive</key>\n <dict>\n <key>SuccessfulExit</key>\n <false/>\n </dict>\n <key>ThrottleInterval</key>\n <integer>5</integer>\n</dict>\n</plist>\n`;
|
|
368
|
+
function agentsTemplate() {
|
|
369
|
+
return [
|
|
370
|
+
"# Personal Workspace",
|
|
371
|
+
"",
|
|
372
|
+
"Use this directory as the default home for personal work.",
|
|
373
|
+
"",
|
|
374
|
+
"- Read `SOUL.md` and `IDENTITY.md` when identity or tone matters.",
|
|
375
|
+
"- Read `USER.md` when stable user preferences affect the task.",
|
|
376
|
+
"- Search `MEMORY.md` and dated files under `memory/` when prior facts or decisions matter.",
|
|
377
|
+
"- Read the selected `skills/<name>/SKILL.md` before following a Workspace Skill.",
|
|
378
|
+
"- Put incoming material in `work/inbox/`, drafts in `work/drafts/`, and durable general outputs in `work/artifacts/`.",
|
|
379
|
+
"- Put disposable files in `work/tmp/`.",
|
|
380
|
+
"- Store only confirmed durable facts in Memory; keep credentials and raw private transcripts out of tracked files.",
|
|
381
|
+
""
|
|
382
|
+
].join("\n");
|
|
514
383
|
}
|
|
515
|
-
|
|
516
|
-
|
|
384
|
+
//#endregion
|
|
385
|
+
//#region src/platform/home/setup/rivus-home-initializer.ts
|
|
386
|
+
function initializeRivusHome(directoryPath) {
|
|
387
|
+
const directory = resolve(directoryPath);
|
|
388
|
+
const { directories, files } = createRivusHomeLayout();
|
|
389
|
+
const paths = [...files.keys()].sort();
|
|
390
|
+
return Effect.tryPromise({
|
|
391
|
+
try: async () => {
|
|
392
|
+
for (const path of [...directories].sort()) await mkdir(join(directory, path), { recursive: true });
|
|
393
|
+
for (const path of paths) {
|
|
394
|
+
const destination = join(directory, path);
|
|
395
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
396
|
+
await writeFile(destination, files.get(path), {
|
|
397
|
+
encoding: "utf8",
|
|
398
|
+
flag: "wx"
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
return {
|
|
402
|
+
directory,
|
|
403
|
+
files: paths
|
|
404
|
+
};
|
|
405
|
+
},
|
|
406
|
+
catch: (error) => error instanceof Error ? error : new Error(String(error))
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
//#endregion
|
|
410
|
+
//#region src/platform/home/workspace/rivus-home-workspace.ts
|
|
411
|
+
function findMissingRivusHomeWorkspacePaths(home) {
|
|
412
|
+
const required = [
|
|
413
|
+
home.workspaceDirectory,
|
|
414
|
+
resolve(home.workspaceDirectory, "AGENTS.md"),
|
|
415
|
+
resolve(home.workspaceDirectory, "MEMORY.md"),
|
|
416
|
+
resolve(home.workspaceDirectory, "memory"),
|
|
417
|
+
resolve(home.workspaceDirectory, "skills"),
|
|
418
|
+
resolve(home.workspaceDirectory, "work")
|
|
419
|
+
];
|
|
420
|
+
return Effect.tryPromise({
|
|
421
|
+
try: async () => {
|
|
422
|
+
const missing = [];
|
|
423
|
+
for (const path of required) try {
|
|
424
|
+
await stat(path);
|
|
425
|
+
} catch (error) {
|
|
426
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") missing.push(path);
|
|
427
|
+
else throw error;
|
|
428
|
+
}
|
|
429
|
+
return missing;
|
|
430
|
+
},
|
|
431
|
+
catch: (error) => error instanceof Error ? error : new Error(String(error))
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
//#endregion
|
|
435
|
+
//#region src/platform/home/node/node-rivus-home.ts
|
|
436
|
+
function createNodeRivusHome(options) {
|
|
437
|
+
return {
|
|
438
|
+
diagnose: (input) => diagnoseRivusHome(input, {
|
|
439
|
+
deploymentInspector: options.deploymentInspector,
|
|
440
|
+
findMissingWorkspacePaths: findMissingRivusHomeWorkspacePaths,
|
|
441
|
+
load: loadRivusHome
|
|
442
|
+
}),
|
|
443
|
+
load: loadRivusHome,
|
|
444
|
+
setup: initializeRivusHome
|
|
445
|
+
};
|
|
517
446
|
}
|
|
518
447
|
//#endregion
|
|
519
|
-
//#region src/
|
|
520
|
-
const
|
|
448
|
+
//#region src/adapters/cli/command/rivus-cli-protocol.ts
|
|
449
|
+
const RIVUS_CLI_USAGE = `Usage:
|
|
521
450
|
rivus setup [directory]
|
|
522
451
|
rivus start
|
|
523
452
|
rivus status
|
|
@@ -537,123 +466,44 @@ Commands:
|
|
|
537
466
|
|
|
538
467
|
Run rivus --help for the complete daemon option list.
|
|
539
468
|
`;
|
|
540
|
-
function
|
|
541
|
-
|
|
542
|
-
if (command === "setup") return runSetupCommand(options);
|
|
543
|
-
if (command === "start") return runHomeDaemonCommand(options, []);
|
|
544
|
-
if (command === "status") return runHomeDaemonCommand(options, ["--status"]);
|
|
545
|
-
if (command === "check-config") return runHomeDaemonCommand(options, ["--check-config"]);
|
|
546
|
-
if (command === "init") return runInitCommand(options);
|
|
547
|
-
if (command === "doctor") return runDoctorCommand(options);
|
|
548
|
-
if (command && !command.startsWith("-")) return Effect.sync(() => {
|
|
549
|
-
options.stderr.write(`Unknown command: ${command}\n\n${USAGE}`);
|
|
550
|
-
return 1;
|
|
551
|
-
});
|
|
552
|
-
return runRivusDaemonCli(options);
|
|
469
|
+
function renderRivusCliError(error) {
|
|
470
|
+
return `${error instanceof Error ? error.message : String(error)}\n`;
|
|
553
471
|
}
|
|
554
|
-
function
|
|
555
|
-
return
|
|
472
|
+
function renderRivusCliUnknownCommand(command) {
|
|
473
|
+
return `Unknown command: ${command}\n\n${RIVUS_CLI_USAGE}`;
|
|
556
474
|
}
|
|
557
|
-
function
|
|
558
|
-
|
|
559
|
-
options.stderr.write(`Usage: rivus ${options.argv[0]}\n`);
|
|
560
|
-
return 1;
|
|
561
|
-
});
|
|
562
|
-
return resolveHomeDirectoryEffect(options).pipe(Effect.flatMap((directory) => options.homeApi.load(directory)), Effect.tap((home) => Effect.sync(() => options.changeWorkingDirectory(home.workspaceDirectory))), Effect.flatMap((home) => runRivusDaemonCli({
|
|
563
|
-
...options,
|
|
564
|
-
argv: [
|
|
565
|
-
"--env-file",
|
|
566
|
-
home.envFilePath,
|
|
567
|
-
"--bootstrap",
|
|
568
|
-
home.bootstrap,
|
|
569
|
-
"--manifest",
|
|
570
|
-
home.manifestPath,
|
|
571
|
-
...daemonArgs
|
|
572
|
-
],
|
|
573
|
-
env: {
|
|
574
|
-
...options.env,
|
|
575
|
-
RIVUS_DEPLOYMENT_STATE_DIR: home.stateDirectory,
|
|
576
|
-
RIVUS_HOME: home.directory
|
|
577
|
-
},
|
|
578
|
-
pluginPackageManifestPath: options.packageManifestPath
|
|
579
|
-
})), Effect.catchAll((error) => writeError(options, error)));
|
|
475
|
+
function renderRivusDirectoryCommandUsage(command) {
|
|
476
|
+
return `Usage: rivus ${command} [directory]\n`;
|
|
580
477
|
}
|
|
581
|
-
function
|
|
582
|
-
return
|
|
583
|
-
const directory = resolve(options.cwd, argument ?? ".");
|
|
584
|
-
return Effect.tryPromise({
|
|
585
|
-
try: () => initializeRivusProject({
|
|
586
|
-
directory,
|
|
587
|
-
nodeExecutable: options.nodeExecutable,
|
|
588
|
-
packageManifestPath: options.packageManifestPath,
|
|
589
|
-
templateDirectory: options.templateDirectory
|
|
590
|
-
}),
|
|
591
|
-
catch: (error) => error
|
|
592
|
-
}).pipe(Effect.tap((result) => Effect.sync(() => options.stdout.write(`Initialized Rivus project in ${result.directory}\n\nNext:\n cd ${shellQuote(result.directory)}\n npm install\n cp .env.example .env.local\n npm run doctor\n npm start\n`))), Effect.as(0), Effect.catchAll((error) => writeError(options, error)));
|
|
593
|
-
});
|
|
478
|
+
function renderRivusHomeCommandUsage(command) {
|
|
479
|
+
return `Usage: rivus ${command}\n`;
|
|
594
480
|
}
|
|
595
|
-
function
|
|
596
|
-
|
|
597
|
-
const usage = `Usage: rivus ${command} [directory]\n`;
|
|
598
|
-
if (args.includes("--help") || args.includes("-h")) return Effect.sync(() => {
|
|
599
|
-
options.stdout.write(usage);
|
|
600
|
-
return 0;
|
|
601
|
-
});
|
|
602
|
-
if (args.length > 1 || args[0]?.startsWith("-")) return Effect.sync(() => {
|
|
603
|
-
options.stderr.write(usage);
|
|
604
|
-
return 1;
|
|
605
|
-
});
|
|
606
|
-
return run(args[0]);
|
|
481
|
+
function renderRivusDoctorUsage() {
|
|
482
|
+
return "Usage: rivus doctor [directory] [--env-file <path>]\n";
|
|
607
483
|
}
|
|
608
|
-
function
|
|
609
|
-
|
|
610
|
-
if (parsed.help) return Effect.sync(() => {
|
|
611
|
-
options.stdout.write("Usage: rivus doctor [directory] [--env-file <path>]\n");
|
|
612
|
-
return 0;
|
|
613
|
-
});
|
|
614
|
-
if (parsed.error) return Effect.sync(() => {
|
|
615
|
-
options.stderr.write(`${parsed.error}\nUsage: rivus doctor [directory] [--env-file <path>]\n`);
|
|
616
|
-
return 1;
|
|
617
|
-
});
|
|
618
|
-
if (parsed.directory === void 0) return resolveHomeDirectoryEffect(options).pipe(Effect.flatMap((directory) => options.homeApi.diagnose({
|
|
619
|
-
directory,
|
|
620
|
-
env: options.env,
|
|
621
|
-
...parsed.envFilePath ? { envFilePath: parsed.envFilePath } : {},
|
|
622
|
-
nodeVersion: options.nodeVersion
|
|
623
|
-
})), Effect.map((report) => writeDoctorReport(options.stdout, report, "Home")), Effect.catchAll((error) => writeError(options, error)));
|
|
624
|
-
const projectDirectory = parsed.directory;
|
|
625
|
-
return diagnoseRivusProject({
|
|
626
|
-
directory: resolve(options.cwd, projectDirectory),
|
|
627
|
-
env: options.env,
|
|
628
|
-
...parsed.envFilePath ? { envFilePath: parsed.envFilePath } : {},
|
|
629
|
-
nodeVersion: options.nodeVersion
|
|
630
|
-
}).pipe(Effect.map((report) => writeDoctorReport(options.stdout, report, "project")), Effect.catchAll((error) => writeError(options, error)));
|
|
484
|
+
function renderRivusDoctorArgumentError(error) {
|
|
485
|
+
return `${error}\n${renderRivusDoctorUsage()}`;
|
|
631
486
|
}
|
|
632
|
-
function
|
|
633
|
-
return
|
|
634
|
-
try: () => {
|
|
635
|
-
if (argument) return resolve(options.cwd, argument);
|
|
636
|
-
const configured = options.env.RIVUS_HOME?.trim();
|
|
637
|
-
if (!configured) return resolve(options.homeDirectory, ".rivus-agent");
|
|
638
|
-
if (!isAbsolute(configured)) throw new Error("RIVUS_HOME must be an absolute path");
|
|
639
|
-
return resolve(configured);
|
|
640
|
-
},
|
|
641
|
-
catch: (error) => error instanceof Error ? error : new Error(String(error))
|
|
642
|
-
});
|
|
487
|
+
function renderRivusSetupSuccess(directory) {
|
|
488
|
+
return `Initialized Rivus Home in ${directory}\n\nNext:\n cp ${shellQuote(`${directory}/.env.example`)} ${shellQuote(`${directory}/.env`)}\n rivus doctor\n rivus start\n`;
|
|
643
489
|
}
|
|
644
|
-
function
|
|
645
|
-
return
|
|
646
|
-
options.stderr.write(`${formatError(error)}\n`);
|
|
647
|
-
return 1;
|
|
648
|
-
});
|
|
490
|
+
function renderRivusProjectInitializationSuccess(directory) {
|
|
491
|
+
return `Initialized Rivus project in ${directory}\n\nNext:\n cd ${shellQuote(directory)}\n npm install\n cp .env.example .env.local\n npm run doctor\n npm start\n`;
|
|
649
492
|
}
|
|
650
|
-
function
|
|
651
|
-
|
|
652
|
-
for (const check of report.checks)
|
|
653
|
-
|
|
654
|
-
|
|
493
|
+
function* renderRivusDoctorReport(report, owner) {
|
|
494
|
+
yield `Rivus doctor: ${report.directory}\n`;
|
|
495
|
+
for (const check of report.checks) yield `${check.status === "pass" ? "PASS" : "FAIL"} ${check.name}: ${check.message}\n`;
|
|
496
|
+
yield report.ready ? `Rivus ${owner} is ready\n` : `Rivus ${owner} is not ready\n`;
|
|
497
|
+
}
|
|
498
|
+
function parseRivusDirectoryArguments(argv) {
|
|
499
|
+
if (argv.includes("--help") || argv.includes("-h")) return { help: true };
|
|
500
|
+
if (argv.length > 1 || argv[0]?.startsWith("-")) return { error: "invalid directory arguments" };
|
|
501
|
+
return argv[0] === void 0 ? {} : { directory: argv[0] };
|
|
655
502
|
}
|
|
656
|
-
function
|
|
503
|
+
function hasRivusHomeCommandArguments(argv) {
|
|
504
|
+
return argv.length > 0;
|
|
505
|
+
}
|
|
506
|
+
function parseRivusDoctorArguments(argv) {
|
|
657
507
|
let directory;
|
|
658
508
|
let envFilePath;
|
|
659
509
|
for (let index = 0; index < argv.length; index += 1) {
|
|
@@ -680,357 +530,559 @@ function parseDoctorArguments(argv) {
|
|
|
680
530
|
...envFilePath !== void 0 ? { envFilePath } : {}
|
|
681
531
|
};
|
|
682
532
|
}
|
|
683
|
-
function formatError(error) {
|
|
684
|
-
return error instanceof Error ? error.message : String(error);
|
|
685
|
-
}
|
|
686
533
|
function shellQuote(value) {
|
|
687
534
|
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
688
535
|
}
|
|
689
536
|
//#endregion
|
|
690
|
-
//#region src/
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
537
|
+
//#region src/adapters/deployment/inspection/rivus-project-doctor.ts
|
|
538
|
+
const REQUIRED_FILES = Object.freeze([
|
|
539
|
+
"package.json",
|
|
540
|
+
"rivus.bootstrap.ts",
|
|
541
|
+
"rivus.config.json"
|
|
542
|
+
]);
|
|
543
|
+
const REQUIRED_DEPENDENCIES = Object.freeze([
|
|
544
|
+
"@rivus/agent",
|
|
545
|
+
"@earendil-works/pi-coding-agent",
|
|
546
|
+
"@larksuiteoapi/node-sdk"
|
|
547
|
+
]);
|
|
548
|
+
function diagnoseRivusProject(options) {
|
|
549
|
+
return Effect.gen(function* () {
|
|
550
|
+
const directory = resolve(options.directory);
|
|
551
|
+
const checks = [checkNode(options.nodeVersion)];
|
|
552
|
+
checks.push(yield* Effect.tryPromise({
|
|
553
|
+
try: () => checkFiles(directory),
|
|
554
|
+
catch: toError
|
|
555
|
+
}));
|
|
556
|
+
checks.push(yield* Effect.tryPromise({
|
|
557
|
+
try: () => checkDependencies(directory),
|
|
558
|
+
catch: toError
|
|
559
|
+
}));
|
|
560
|
+
const manifestResult = yield* checkManifest(directory);
|
|
561
|
+
checks.push(manifestResult.check);
|
|
562
|
+
const envFilePath = resolve(directory, options.envFilePath ?? ".env.local");
|
|
563
|
+
checks.push(yield* Effect.tryPromise({
|
|
564
|
+
try: () => checkCredentials(manifestResult.manifest, envFilePath, options.env),
|
|
565
|
+
catch: toError
|
|
566
|
+
}));
|
|
567
|
+
return Object.freeze({
|
|
568
|
+
checks: Object.freeze(checks),
|
|
569
|
+
directory,
|
|
570
|
+
ready: checks.every(({ status }) => status === "pass")
|
|
571
|
+
});
|
|
711
572
|
});
|
|
712
573
|
}
|
|
713
|
-
function
|
|
714
|
-
const
|
|
715
|
-
if (
|
|
574
|
+
function checkNode(version) {
|
|
575
|
+
const [major, minor] = version.split(".").map(Number);
|
|
576
|
+
if (major === 24 && Number.isInteger(minor) && minor >= 11) return {
|
|
577
|
+
message: `Node.js ${version} satisfies the supported ^24.11 runtime`,
|
|
578
|
+
name: "node",
|
|
579
|
+
status: "pass"
|
|
580
|
+
};
|
|
716
581
|
return {
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
manifest: relativePath(config.manifest, "manifest"),
|
|
721
|
-
state: relativePath(config.state, "state"),
|
|
722
|
-
version: 1,
|
|
723
|
-
workspace: relativePath(config.workspace, "workspace")
|
|
582
|
+
message: `Node.js 24.11 or newer on major 24 is required; current version is ${version}`,
|
|
583
|
+
name: "node",
|
|
584
|
+
status: "fail"
|
|
724
585
|
};
|
|
725
586
|
}
|
|
726
|
-
function
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
}
|
|
737
|
-
function relativePath(value, owner) {
|
|
738
|
-
const path = nonEmptyString(value, owner);
|
|
739
|
-
if (isAbsolute(path)) throw new Error(`Rivus Home ${owner} must be a relative path`);
|
|
740
|
-
return path;
|
|
741
|
-
}
|
|
742
|
-
function nonEmptyString(value, owner) {
|
|
743
|
-
if (typeof value !== "string" || !value.trim()) throw new Error(`Rivus Home ${owner} must be a non-empty string`);
|
|
744
|
-
return value;
|
|
587
|
+
async function checkFiles(directory) {
|
|
588
|
+
const missing = await missingRegularFiles(directory, REQUIRED_FILES);
|
|
589
|
+
return missing.length === 0 ? {
|
|
590
|
+
message: "required project files are present",
|
|
591
|
+
name: "files",
|
|
592
|
+
status: "pass"
|
|
593
|
+
} : {
|
|
594
|
+
message: `required project files are missing: ${missing.join(", ")}`,
|
|
595
|
+
name: "files",
|
|
596
|
+
status: "fail"
|
|
597
|
+
};
|
|
745
598
|
}
|
|
746
|
-
function
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
//#endregion
|
|
754
|
-
//#region src/platform/home/doctor/rivus-home-doctor.ts
|
|
755
|
-
function diagnoseRivusHome(input, options) {
|
|
756
|
-
return Effect.gen(function* () {
|
|
757
|
-
const checks = [checkNode(input.nodeVersion)];
|
|
758
|
-
const loaded = yield* options.load(input.directory).pipe(Effect.either);
|
|
759
|
-
if (Either.isLeft(loaded)) {
|
|
760
|
-
checks.push({
|
|
761
|
-
message: `Rivus Home is invalid: ${loaded.left.message}`,
|
|
762
|
-
name: "home",
|
|
763
|
-
status: "fail"
|
|
764
|
-
}, blockedCheck("workspace", "Workspace"), blockedCheck("manifest", "manifest"), blockedCheck("modules", "modules"), blockedCheck("credentials", "credentials"));
|
|
765
|
-
return report(input.directory, checks);
|
|
766
|
-
}
|
|
767
|
-
const home = loaded.right;
|
|
768
|
-
checks.push({
|
|
769
|
-
message: "config.json is valid and contained in Rivus Home",
|
|
770
|
-
name: "home",
|
|
771
|
-
status: "pass"
|
|
772
|
-
});
|
|
773
|
-
checks.push(yield* checkWorkspace(home, options.findMissingWorkspacePaths));
|
|
774
|
-
const deployment = yield* options.deploymentInspector.inspect({
|
|
775
|
-
env: input.env,
|
|
776
|
-
...input.envFilePath ? { envFilePath: input.envFilePath } : {},
|
|
777
|
-
home
|
|
778
|
-
});
|
|
779
|
-
checks.push(deployment.manifest, deployment.modules, deployment.credentials);
|
|
780
|
-
return report(input.directory, checks);
|
|
781
|
-
});
|
|
782
|
-
}
|
|
783
|
-
function checkNode(version) {
|
|
784
|
-
const [major, minor] = version.split(".").map(Number);
|
|
785
|
-
return major === 24 && Number.isInteger(minor) && minor >= 11 ? {
|
|
786
|
-
message: `Node.js ${version} satisfies the supported ^24.11 runtime`,
|
|
787
|
-
name: "node",
|
|
599
|
+
async function checkDependencies(directory) {
|
|
600
|
+
const packageFiles = REQUIRED_DEPENDENCIES.map((name) => join("node_modules", ...name.split("/"), "package.json"));
|
|
601
|
+
const missingIndexes = new Set((await missingRegularFiles(directory, packageFiles)).map((path) => packageFiles.indexOf(path)));
|
|
602
|
+
const missing = REQUIRED_DEPENDENCIES.filter((_, index) => missingIndexes.has(index));
|
|
603
|
+
return missing.length === 0 ? {
|
|
604
|
+
message: "Rivus, Pi, and Feishu dependencies are installed",
|
|
605
|
+
name: "dependencies",
|
|
788
606
|
status: "pass"
|
|
789
607
|
} : {
|
|
790
|
-
message: `
|
|
791
|
-
name: "
|
|
608
|
+
message: `run npm install; missing local dependencies: ${missing.join(", ")}`,
|
|
609
|
+
name: "dependencies",
|
|
792
610
|
status: "fail"
|
|
793
611
|
};
|
|
794
612
|
}
|
|
795
|
-
function
|
|
796
|
-
return
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
613
|
+
function checkManifest(directory) {
|
|
614
|
+
return loadRivusDeploymentManifest(join(directory, "rivus.config.json")).pipe(Effect.flatMap((manifest) => Effect.gen(function* () {
|
|
615
|
+
validateRivusDeploymentManifest(manifest);
|
|
616
|
+
const missingModules = [];
|
|
617
|
+
for (const plugin of manifest.plugins) {
|
|
618
|
+
const result = yield* resolveNodeRivusPluginModulePath({
|
|
619
|
+
deploymentRoot: directory,
|
|
620
|
+
module: plugin.module,
|
|
621
|
+
pluginId: plugin.id
|
|
622
|
+
}).pipe(Effect.either);
|
|
623
|
+
if (Either.isLeft(result)) missingModules.push(plugin.module);
|
|
624
|
+
}
|
|
625
|
+
if (missingModules.length > 0) return { check: {
|
|
626
|
+
message: `manifest Plugin modules are missing: ${missingModules.join(", ")}`,
|
|
627
|
+
name: "manifest",
|
|
628
|
+
status: "fail"
|
|
629
|
+
} };
|
|
630
|
+
return {
|
|
631
|
+
check: {
|
|
632
|
+
message: "deployment manifest is valid",
|
|
633
|
+
name: "manifest",
|
|
634
|
+
status: "pass"
|
|
635
|
+
},
|
|
636
|
+
manifest
|
|
637
|
+
};
|
|
638
|
+
})), Effect.catchAll((error) => Effect.succeed({ check: {
|
|
639
|
+
message: `deployment manifest is invalid: ${error.message}`,
|
|
640
|
+
name: "manifest",
|
|
803
641
|
status: "fail"
|
|
804
|
-
}));
|
|
642
|
+
} })));
|
|
805
643
|
}
|
|
806
|
-
function
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
644
|
+
async function checkCredentials(manifest, envFilePath, env) {
|
|
645
|
+
let mergedEnv;
|
|
646
|
+
try {
|
|
647
|
+
mergedEnv = await loadMergedLocalEnvFile(envFilePath, env);
|
|
648
|
+
} catch (error) {
|
|
649
|
+
return {
|
|
650
|
+
message: `required env file is unavailable at ${envFilePath}: ${error instanceof Error ? error.message : String(error)}`,
|
|
651
|
+
name: "credentials",
|
|
652
|
+
status: "fail"
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
if (!manifest) return {
|
|
656
|
+
message: "enabled Endpoint credentials cannot be checked until the deployment manifest is valid",
|
|
657
|
+
name: "credentials",
|
|
810
658
|
status: "fail"
|
|
811
659
|
};
|
|
660
|
+
try {
|
|
661
|
+
for (const endpoint of manifest.endpoints.filter(({ enabled }) => enabled)) resolveFeishuEndpointCredentials(endpoint.credentialRef, mergedEnv);
|
|
662
|
+
return {
|
|
663
|
+
message: "enabled Endpoint credential references resolve",
|
|
664
|
+
name: "credentials",
|
|
665
|
+
status: "pass"
|
|
666
|
+
};
|
|
667
|
+
} catch (error) {
|
|
668
|
+
return {
|
|
669
|
+
message: `enabled Endpoint credentials are incomplete: ${error instanceof Error ? error.message : String(error)}`,
|
|
670
|
+
name: "credentials",
|
|
671
|
+
status: "fail"
|
|
672
|
+
};
|
|
673
|
+
}
|
|
812
674
|
}
|
|
813
|
-
function
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
directory,
|
|
817
|
-
|
|
818
|
-
|
|
675
|
+
async function missingRegularFiles(directory, paths) {
|
|
676
|
+
const missing = [];
|
|
677
|
+
for (const path of paths) try {
|
|
678
|
+
if (!(await stat(resolve(directory, path))).isFile()) missing.push(path);
|
|
679
|
+
} catch (error) {
|
|
680
|
+
if (!isMissingPath$1(error)) throw error;
|
|
681
|
+
missing.push(path);
|
|
682
|
+
}
|
|
683
|
+
return missing;
|
|
684
|
+
}
|
|
685
|
+
function isMissingPath$1(error) {
|
|
686
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
687
|
+
}
|
|
688
|
+
function toError(error) {
|
|
689
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
819
690
|
}
|
|
820
691
|
//#endregion
|
|
821
|
-
//#region src/platform/
|
|
822
|
-
const
|
|
823
|
-
"
|
|
824
|
-
"
|
|
825
|
-
"
|
|
826
|
-
"
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
]
|
|
833
|
-
|
|
692
|
+
//#region src/platform/project/setup/rivus-project-initializer.ts
|
|
693
|
+
const TEMPLATE_FILES = Object.freeze({
|
|
694
|
+
"current-weather.mjs": "current-weather.mjs",
|
|
695
|
+
"https-response-reader.mjs": "https-response-reader.mjs",
|
|
696
|
+
"rivus-agents.plugin.mjs": "rivus-starter.plugin.mjs",
|
|
697
|
+
"rivus.bootstrap.ts": "pi-feishu-deployment.bootstrap.ts"
|
|
698
|
+
});
|
|
699
|
+
async function initializeRivusProject(options) {
|
|
700
|
+
const directory = resolve(options.directory);
|
|
701
|
+
const manifest = await readPackageManifest(options.packageManifestPath);
|
|
702
|
+
const files = /* @__PURE__ */ new Map([
|
|
703
|
+
[".env.example", environmentTemplate()],
|
|
704
|
+
[".gitignore", ".env.local\n.rivus/\nnode_modules/\n"],
|
|
705
|
+
["deploy/launchd/com.rivus.agent.plist", launchdService(directory, options.nodeExecutable)],
|
|
706
|
+
["deploy/systemd/rivus.service", systemdService(directory, options.nodeExecutable)],
|
|
707
|
+
["package.json", projectPackageJson(directory, manifest)],
|
|
708
|
+
["rivus.config.json", deploymentManifest()]
|
|
709
|
+
]);
|
|
710
|
+
for (const [target, source] of Object.entries(TEMPLATE_FILES)) files.set(target, await readFile(join(options.templateDirectory, source), "utf8"));
|
|
711
|
+
const paths = [...files.keys()].sort();
|
|
712
|
+
await assertSafeProjectAncestors(directory, paths);
|
|
713
|
+
const conflicts = await findConflicts(directory, paths);
|
|
714
|
+
if (conflicts.length > 0) throw new Error(`Rivus project initialization refused; refusing to overwrite: ${conflicts.join(", ")}`);
|
|
715
|
+
const createdDirectories = [];
|
|
716
|
+
const createdFiles = [];
|
|
717
|
+
const writeProjectFile = options.writeProjectFile ?? writeExclusiveProjectFile;
|
|
718
|
+
try {
|
|
719
|
+
for (const path of paths) {
|
|
720
|
+
const destination = join(directory, path);
|
|
721
|
+
await ensureDirectory(dirname(destination), directory, createdDirectories);
|
|
722
|
+
await assertSafeProjectAncestors(directory, [path]);
|
|
723
|
+
await writeProjectFile(destination, files.get(path));
|
|
724
|
+
createdFiles.push(destination);
|
|
725
|
+
}
|
|
726
|
+
} catch (error) {
|
|
727
|
+
const rollbackErrors = await rollbackCreatedPaths(createdFiles, createdDirectories);
|
|
728
|
+
if (rollbackErrors.length > 0) throw new AggregateError([error, ...rollbackErrors], "Rivus project initialization failed and rollback was incomplete");
|
|
729
|
+
throw error;
|
|
730
|
+
}
|
|
834
731
|
return {
|
|
835
|
-
|
|
836
|
-
files:
|
|
837
|
-
[".env.example", environmentTemplate()],
|
|
838
|
-
[".gitignore", ".DS_Store\n.env\nlogs/\nstate/\nworkspace/work/tmp/\n"],
|
|
839
|
-
["config.json", homeConfig()],
|
|
840
|
-
["rivus.config.json", deploymentManifest()],
|
|
841
|
-
["workspace/AGENTS.md", agentsTemplate()],
|
|
842
|
-
["workspace/IDENTITY.md", "# Identity\n\nName: Rivus\n\nRole: Personal agent\n"],
|
|
843
|
-
["workspace/MEMORY.md", "# Curated Memory\n\nStore confirmed, durable facts and decisions here.\n"],
|
|
844
|
-
["workspace/SOUL.md", "# Character\n\nBe direct, thoughtful, and evidence-led.\n"],
|
|
845
|
-
["workspace/USER.md", "# User\n\nRecord stable user preferences here.\n"],
|
|
846
|
-
["plugins/.gitkeep", ""],
|
|
847
|
-
["workspace/memory/.gitkeep", ""],
|
|
848
|
-
["workspace/skills/.gitkeep", ""],
|
|
849
|
-
["workspace/work/artifacts/.gitkeep", ""],
|
|
850
|
-
["workspace/work/drafts/.gitkeep", ""],
|
|
851
|
-
["workspace/work/inbox/.gitkeep", ""]
|
|
852
|
-
])
|
|
732
|
+
directory,
|
|
733
|
+
files: Object.freeze(paths)
|
|
853
734
|
};
|
|
854
735
|
}
|
|
855
|
-
function
|
|
736
|
+
async function writeExclusiveProjectFile(path, contents) {
|
|
737
|
+
await writeFile(path, contents, {
|
|
738
|
+
encoding: "utf8",
|
|
739
|
+
flag: "wx"
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
async function assertSafeProjectAncestors(directory, paths) {
|
|
743
|
+
const rootState = await lstatOrUndefined(directory);
|
|
744
|
+
if (rootState?.isSymbolicLink()) throw new Error("Rivus project initialization refused; symbolic link ancestor: .");
|
|
745
|
+
if (rootState && !rootState.isDirectory()) throw new Error("Rivus project initialization refused; project path is not a directory");
|
|
746
|
+
if (!rootState) return;
|
|
747
|
+
for (const path of paths) {
|
|
748
|
+
let current = directory;
|
|
749
|
+
for (const segment of path.split("/").slice(0, -1)) {
|
|
750
|
+
current = join(current, segment);
|
|
751
|
+
const state = await lstatOrUndefined(current);
|
|
752
|
+
if (!state) break;
|
|
753
|
+
if (state.isSymbolicLink()) throw new Error(`Rivus project initialization refused; symbolic link ancestor: ${relative(directory, current)}`);
|
|
754
|
+
if (!state.isDirectory()) throw new Error(`Rivus project initialization refused; non-directory ancestor: ${relative(directory, current)}`);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
async function ensureDirectory(path, projectRoot, createdDirectories) {
|
|
759
|
+
try {
|
|
760
|
+
await mkdir(path);
|
|
761
|
+
createdDirectories.push(path);
|
|
762
|
+
} catch (error) {
|
|
763
|
+
if (isMissingPath(error)) {
|
|
764
|
+
const parent = dirname(path);
|
|
765
|
+
if (parent === path) throw error;
|
|
766
|
+
await ensureDirectory(parent, projectRoot, createdDirectories);
|
|
767
|
+
await ensureDirectory(path, projectRoot, createdDirectories);
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
if (!isAlreadyExists(error)) throw error;
|
|
771
|
+
const state = await lstat(path);
|
|
772
|
+
if (state.isSymbolicLink()) {
|
|
773
|
+
if (isPathWithin(projectRoot, path)) throw new Error(`Rivus project initialization refused; symbolic link ancestor: ${relative(projectRoot, path) || "."}`);
|
|
774
|
+
if ((await stat(path)).isDirectory()) return;
|
|
775
|
+
}
|
|
776
|
+
if (!state.isDirectory()) throw new Error(`Rivus project initialization refused; non-directory ancestor: ${path}`);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
async function rollbackCreatedPaths(files, directories) {
|
|
780
|
+
const errors = [];
|
|
781
|
+
for (const path of files.reverse()) try {
|
|
782
|
+
await unlink(path);
|
|
783
|
+
} catch (error) {
|
|
784
|
+
if (!isMissingPath(error)) errors.push(asError(error));
|
|
785
|
+
}
|
|
786
|
+
for (const path of directories.reverse()) try {
|
|
787
|
+
await rmdir(path);
|
|
788
|
+
} catch (error) {
|
|
789
|
+
if (!isMissingPath(error) && !isDirectoryNotEmpty(error)) errors.push(asError(error));
|
|
790
|
+
}
|
|
791
|
+
return errors;
|
|
792
|
+
}
|
|
793
|
+
async function lstatOrUndefined(path) {
|
|
794
|
+
try {
|
|
795
|
+
return await lstat(path);
|
|
796
|
+
} catch (error) {
|
|
797
|
+
if (isMissingPath(error)) return void 0;
|
|
798
|
+
throw error;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
function isAlreadyExists(error) {
|
|
802
|
+
return error instanceof Error && "code" in error && error.code === "EEXIST";
|
|
803
|
+
}
|
|
804
|
+
function isDirectoryNotEmpty(error) {
|
|
805
|
+
return error instanceof Error && "code" in error && error.code === "ENOTEMPTY";
|
|
806
|
+
}
|
|
807
|
+
function asError(error) {
|
|
808
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
809
|
+
}
|
|
810
|
+
async function readPackageManifest(path) {
|
|
811
|
+
const manifest = JSON.parse(await readFile(path, "utf8"));
|
|
812
|
+
if (manifest.name !== "@rivus/agent" || typeof manifest.version !== "string") throw new Error("Rivus package manifest is missing its release identity");
|
|
813
|
+
return manifest;
|
|
814
|
+
}
|
|
815
|
+
async function findConflicts(directory, paths) {
|
|
816
|
+
const conflicts = [];
|
|
817
|
+
for (const path of paths) try {
|
|
818
|
+
await lstat(join(directory, path));
|
|
819
|
+
conflicts.push(path);
|
|
820
|
+
} catch (error) {
|
|
821
|
+
if (!isMissingPath(error)) throw error;
|
|
822
|
+
}
|
|
823
|
+
return conflicts;
|
|
824
|
+
}
|
|
825
|
+
function isMissingPath(error) {
|
|
826
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
827
|
+
}
|
|
828
|
+
function projectPackageJson(directory, manifest) {
|
|
829
|
+
const pi = manifest.dependencies?.["@earendil-works/pi-coding-agent"];
|
|
830
|
+
const lark = manifest.dependencies?.["@larksuiteoapi/node-sdk"];
|
|
831
|
+
const effect = manifest.dependencies?.effect;
|
|
832
|
+
if (!pi || !lark || !effect) throw new Error("Rivus package manifest is missing its Effect, Pi, or Feishu dependency range");
|
|
833
|
+
const projectName = sanitizePackageName(basename(directory));
|
|
856
834
|
return `${JSON.stringify({
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
835
|
+
name: projectName,
|
|
836
|
+
private: true,
|
|
837
|
+
type: "module",
|
|
838
|
+
scripts: {
|
|
839
|
+
"check-config": "rivus --manifest ./rivus.config.json --check-config",
|
|
840
|
+
doctor: "rivus doctor .",
|
|
841
|
+
start: "rivus --env-file .env.local --bootstrap ./rivus.bootstrap.ts --manifest ./rivus.config.json"
|
|
842
|
+
},
|
|
843
|
+
dependencies: {
|
|
844
|
+
"@earendil-works/pi-coding-agent": pi,
|
|
845
|
+
"@larksuiteoapi/node-sdk": lark,
|
|
846
|
+
"@rivus/agent": `^${manifest.version}`,
|
|
847
|
+
effect
|
|
848
|
+
}
|
|
864
849
|
}, null, 2)}\n`;
|
|
865
850
|
}
|
|
851
|
+
function sanitizePackageName(value) {
|
|
852
|
+
return value.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[._-]+|[._-]+$/g, "") || "rivus-app";
|
|
853
|
+
}
|
|
866
854
|
function deploymentManifest() {
|
|
867
855
|
return `${JSON.stringify({
|
|
856
|
+
plugins: [{
|
|
857
|
+
id: "rivus-starter",
|
|
858
|
+
module: "./rivus-agents.plugin.mjs",
|
|
859
|
+
required: true
|
|
860
|
+
}],
|
|
868
861
|
agents: [{
|
|
869
|
-
agentId: "
|
|
870
|
-
endpointIds: ["
|
|
871
|
-
memory: {
|
|
872
|
-
scopes: ["agent-private"],
|
|
873
|
-
tool: true
|
|
874
|
-
},
|
|
862
|
+
agentId: "agent-a",
|
|
863
|
+
endpointIds: ["feishu-agent-a"],
|
|
875
864
|
pluginId: "rivus-starter",
|
|
876
865
|
profileId: "agent-a",
|
|
877
|
-
projectSpaceId: "personal-home",
|
|
878
|
-
runtimeTools: { allow: [
|
|
879
|
-
"read",
|
|
880
|
-
"bash",
|
|
881
|
-
"edit",
|
|
882
|
-
"write",
|
|
883
|
-
"grep",
|
|
884
|
-
"find",
|
|
885
|
-
"ls"
|
|
886
|
-
] },
|
|
887
866
|
skills: { allow: [] },
|
|
888
867
|
tools: { allow: ["rivus-starter/current-weather"] }
|
|
889
868
|
}],
|
|
890
|
-
defaultAgentId: "
|
|
891
|
-
defaultEndpointId: "
|
|
869
|
+
defaultAgentId: "agent-a",
|
|
870
|
+
defaultEndpointId: "feishu-agent-a",
|
|
892
871
|
endpoints: [{
|
|
893
|
-
agentId: "
|
|
872
|
+
agentId: "agent-a",
|
|
894
873
|
baseUrl: "https://open.feishu.cn",
|
|
895
874
|
cardStreamLeaseMs: 51e4,
|
|
896
875
|
credentialRef: "env:RIVUS_FEISHU",
|
|
897
876
|
enabled: true,
|
|
898
877
|
experimental: { cotMessages: false },
|
|
899
878
|
groupPolicy: "mention-only",
|
|
900
|
-
id: "
|
|
879
|
+
id: "feishu-agent-a",
|
|
901
880
|
progressDisplay: "collapsed",
|
|
902
881
|
required: true,
|
|
903
|
-
sessionNamespace: "rivus-
|
|
882
|
+
sessionNamespace: "rivus-starter",
|
|
904
883
|
streamMinIntervalMs: 200
|
|
905
|
-
}],
|
|
906
|
-
plugins: [{
|
|
907
|
-
id: "rivus-starter",
|
|
908
|
-
module: "@rivus/agent/plugin/starter",
|
|
909
|
-
required: true
|
|
910
|
-
}],
|
|
911
|
-
projectSpaces: [{
|
|
912
|
-
id: "personal-home",
|
|
913
|
-
root: "workspace",
|
|
914
|
-
skills: { sources: ["skills"] },
|
|
915
|
-
workingDirectory: "."
|
|
916
884
|
}]
|
|
917
885
|
}, null, 2)}\n`;
|
|
918
886
|
}
|
|
919
887
|
function environmentTemplate() {
|
|
920
888
|
return [
|
|
921
|
-
"# Copy this file to .env and keep the real values untracked.",
|
|
889
|
+
"# Copy this file to .env.local and keep the real values untracked.",
|
|
922
890
|
"RIVUS_FEISHU_APP_ID=",
|
|
923
891
|
"RIVUS_FEISHU_APP_SECRET=",
|
|
924
892
|
"PI_MODEL=",
|
|
925
893
|
"PI_API_KEY=",
|
|
926
894
|
"# PI_BASE_URL=",
|
|
927
895
|
"RIVUS_WEATHER_DEFAULT_LOCATION=上海",
|
|
896
|
+
"# LANGFUSE_BASE_URL=https://jp.cloud.langfuse.com",
|
|
897
|
+
"# LANGFUSE_PUBLIC_KEY=",
|
|
898
|
+
"# LANGFUSE_SECRET_KEY=",
|
|
899
|
+
"# RIVUS_TELEMETRY_CONTENT=redacted",
|
|
928
900
|
""
|
|
929
901
|
].join("\n");
|
|
930
902
|
}
|
|
931
|
-
function
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
""
|
|
945
|
-
|
|
903
|
+
function systemdService(directory, nodeExecutable) {
|
|
904
|
+
const cli = join(directory, "node_modules", "@rivus", "agent", "dist", "cli.js");
|
|
905
|
+
return `[Unit]\nDescription=Rivus Agent\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\nWorkingDirectory=${systemdPath(directory)}\nExecStart=${systemdQuote(nodeExecutable)} ${systemdQuote(cli)} --env-file .env.local --bootstrap ./rivus.bootstrap.ts --manifest ./rivus.config.json\nRestart=on-failure\nRestartSec=5\nEnvironment=NODE_ENV=production\n\n[Install]\nWantedBy=default.target\n`;
|
|
906
|
+
}
|
|
907
|
+
function systemdPath(value) {
|
|
908
|
+
return value.replaceAll("%", "%%");
|
|
909
|
+
}
|
|
910
|
+
function systemdQuote(value) {
|
|
911
|
+
return `"${value.replace(/%/g, "%%").replace(/\$/g, () => "$$").replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
|
|
912
|
+
}
|
|
913
|
+
function launchdService(directory, nodeExecutable) {
|
|
914
|
+
return `<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0">\n<dict>\n <key>Label</key>\n <string>com.rivus.agent</string>\n <key>ProgramArguments</key>\n <array>\n${[
|
|
915
|
+
nodeExecutable,
|
|
916
|
+
join(directory, "node_modules", "@rivus", "agent", "dist", "cli.js"),
|
|
917
|
+
"--env-file",
|
|
918
|
+
".env.local",
|
|
919
|
+
"--bootstrap",
|
|
920
|
+
"./rivus.bootstrap.ts",
|
|
921
|
+
"--manifest",
|
|
922
|
+
"./rivus.config.json"
|
|
923
|
+
].map((value) => ` <string>${xmlEscape(value)}</string>`).join("\n")}\n </array>\n <key>WorkingDirectory</key>\n <string>${xmlEscape(directory)}</string>\n <key>RunAtLoad</key>\n <true/>\n <key>KeepAlive</key>\n <dict>\n <key>SuccessfulExit</key>\n <false/>\n </dict>\n <key>ThrottleInterval</key>\n <integer>5</integer>\n</dict>\n</plist>\n`;
|
|
924
|
+
}
|
|
925
|
+
function xmlEscape(value) {
|
|
926
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
946
927
|
}
|
|
947
928
|
//#endregion
|
|
948
|
-
//#region src/
|
|
949
|
-
function
|
|
950
|
-
const
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
return
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
929
|
+
//#region src/bootstrap/cli/rivus-cli.ts
|
|
930
|
+
function runRivusCli(options) {
|
|
931
|
+
const command = options.argv[0];
|
|
932
|
+
if (command === "setup") return runSetupCommand(options);
|
|
933
|
+
if (command === "start") return runHomeDaemonCommand(options, "start", []);
|
|
934
|
+
if (command === "status") return runHomeDaemonCommand(options, "status", ["--status"]);
|
|
935
|
+
if (command === "check-config") return runHomeDaemonCommand(options, "check-config", ["--check-config"]);
|
|
936
|
+
if (command === "init") return runInitCommand(options);
|
|
937
|
+
if (command === "doctor") return runDoctorCommand(options);
|
|
938
|
+
if (command && !command.startsWith("-")) return Effect.sync(() => {
|
|
939
|
+
options.stderr.write(renderRivusCliUnknownCommand(command));
|
|
940
|
+
return 1;
|
|
941
|
+
});
|
|
942
|
+
return runRivusDaemonCli(options);
|
|
943
|
+
}
|
|
944
|
+
function runSetupCommand(options) {
|
|
945
|
+
return withOptionalDirectoryArgument(options, "setup", (argument) => resolveHomeDirectoryEffect(options, argument).pipe(Effect.flatMap((directory) => options.homeApi.setup(directory).pipe(Effect.tap(() => Effect.sync(() => options.stdout.write(renderRivusSetupSuccess(directory)))), Effect.as(0))), Effect.catchAll((error) => writeError(options, error))));
|
|
946
|
+
}
|
|
947
|
+
function runHomeDaemonCommand(options, command, daemonArgs) {
|
|
948
|
+
if (hasRivusHomeCommandArguments(options.argv.slice(1))) return Effect.sync(() => {
|
|
949
|
+
options.stderr.write(renderRivusHomeCommandUsage(command));
|
|
950
|
+
return 1;
|
|
951
|
+
});
|
|
952
|
+
return resolveHomeDirectoryEffect(options).pipe(Effect.flatMap((directory) => options.homeApi.load(directory)), Effect.tap((home) => Effect.sync(() => options.changeWorkingDirectory(home.workspaceDirectory))), Effect.flatMap((home) => runRivusDaemonCli({
|
|
953
|
+
...options,
|
|
954
|
+
argv: [
|
|
955
|
+
"--env-file",
|
|
956
|
+
home.envFilePath,
|
|
957
|
+
"--bootstrap",
|
|
958
|
+
home.bootstrap,
|
|
959
|
+
"--manifest",
|
|
960
|
+
home.manifestPath,
|
|
961
|
+
...daemonArgs
|
|
962
|
+
],
|
|
963
|
+
env: {
|
|
964
|
+
...options.env,
|
|
965
|
+
RIVUS_DEPLOYMENT_STATE_DIR: home.stateDirectory,
|
|
966
|
+
RIVUS_HOME: home.directory
|
|
968
967
|
},
|
|
969
|
-
|
|
968
|
+
pluginPackageManifestPath: options.packageManifestPath
|
|
969
|
+
})), Effect.catchAll((error) => writeError(options, error)));
|
|
970
|
+
}
|
|
971
|
+
function runInitCommand(options) {
|
|
972
|
+
return withOptionalDirectoryArgument(options, "init", (argument) => {
|
|
973
|
+
const directory = resolve(options.cwd, argument ?? ".");
|
|
974
|
+
return Effect.tryPromise({
|
|
975
|
+
try: () => initializeRivusProject({
|
|
976
|
+
directory,
|
|
977
|
+
nodeExecutable: options.nodeExecutable,
|
|
978
|
+
packageManifestPath: options.packageManifestPath,
|
|
979
|
+
templateDirectory: options.templateDirectory
|
|
980
|
+
}),
|
|
981
|
+
catch: (error) => error
|
|
982
|
+
}).pipe(Effect.tap((result) => Effect.sync(() => options.stdout.write(renderRivusProjectInitializationSuccess(result.directory)))), Effect.as(0), Effect.catchAll((error) => writeError(options, error)));
|
|
970
983
|
});
|
|
971
984
|
}
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
985
|
+
function withOptionalDirectoryArgument(options, command, run) {
|
|
986
|
+
const parsed = parseRivusDirectoryArguments(options.argv.slice(1));
|
|
987
|
+
if (parsed.help) return Effect.sync(() => {
|
|
988
|
+
options.stdout.write(renderRivusDirectoryCommandUsage(command));
|
|
989
|
+
return 0;
|
|
990
|
+
});
|
|
991
|
+
if (parsed.error) return Effect.sync(() => {
|
|
992
|
+
options.stderr.write(renderRivusDirectoryCommandUsage(command));
|
|
993
|
+
return 1;
|
|
994
|
+
});
|
|
995
|
+
return run(parsed.directory);
|
|
996
|
+
}
|
|
997
|
+
function runDoctorCommand(options) {
|
|
998
|
+
const parsed = parseRivusDoctorArguments(options.argv.slice(1));
|
|
999
|
+
if (parsed.help) return Effect.sync(() => {
|
|
1000
|
+
options.stdout.write(renderRivusDoctorUsage());
|
|
1001
|
+
return 0;
|
|
1002
|
+
});
|
|
1003
|
+
if (parsed.error) {
|
|
1004
|
+
const error = parsed.error;
|
|
1005
|
+
return Effect.sync(() => {
|
|
1006
|
+
options.stderr.write(renderRivusDoctorArgumentError(error));
|
|
1007
|
+
return 1;
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
if (parsed.directory === void 0) return resolveHomeDirectoryEffect(options).pipe(Effect.flatMap((directory) => options.homeApi.diagnose({
|
|
1011
|
+
directory,
|
|
1012
|
+
env: options.env,
|
|
1013
|
+
...parsed.envFilePath ? { envFilePath: parsed.envFilePath } : {},
|
|
1014
|
+
nodeVersion: options.nodeVersion
|
|
1015
|
+
})), Effect.map((report) => writeDoctorReport(options.stdout, report, "Home")), Effect.catchAll((error) => writeError(options, error)));
|
|
1016
|
+
const projectDirectory = parsed.directory;
|
|
1017
|
+
return diagnoseRivusProject({
|
|
1018
|
+
directory: resolve(options.cwd, projectDirectory),
|
|
1019
|
+
env: options.env,
|
|
1020
|
+
...parsed.envFilePath ? { envFilePath: parsed.envFilePath } : {},
|
|
1021
|
+
nodeVersion: options.nodeVersion
|
|
1022
|
+
}).pipe(Effect.map((report) => writeDoctorReport(options.stdout, report, "project")), Effect.catchAll((error) => writeError(options, error)));
|
|
1023
|
+
}
|
|
1024
|
+
function resolveHomeDirectoryEffect(options, argument) {
|
|
1025
|
+
return Effect.try({
|
|
1026
|
+
try: () => {
|
|
1027
|
+
if (argument) return resolve(options.cwd, argument);
|
|
1028
|
+
const configured = options.env.RIVUS_HOME?.trim();
|
|
1029
|
+
if (!configured) return resolve(options.homeDirectory, ".rivus-agent");
|
|
1030
|
+
if (!isAbsolute(configured)) throw new Error("RIVUS_HOME must be an absolute path");
|
|
1031
|
+
return resolve(configured);
|
|
993
1032
|
},
|
|
994
1033
|
catch: (error) => error instanceof Error ? error : new Error(String(error))
|
|
995
1034
|
});
|
|
996
1035
|
}
|
|
1036
|
+
function writeDoctorReport(stdout, report, owner) {
|
|
1037
|
+
for (const chunk of renderRivusDoctorReport(report, owner)) stdout.write(chunk);
|
|
1038
|
+
return report.ready ? 0 : 1;
|
|
1039
|
+
}
|
|
1040
|
+
function writeError(options, error) {
|
|
1041
|
+
return Effect.sync(() => {
|
|
1042
|
+
options.stderr.write(renderRivusCliError(error));
|
|
1043
|
+
return 1;
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
997
1046
|
//#endregion
|
|
998
|
-
//#region src/
|
|
999
|
-
function
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1047
|
+
//#region src/bootstrap/cli/rivus-node-entrypoint.ts
|
|
1048
|
+
function runRivusNodeEntrypoint(options) {
|
|
1049
|
+
const packageManifestPath = join(options.packageDirectory, "package.json");
|
|
1050
|
+
return runRivusCli({
|
|
1051
|
+
argv: options.argv,
|
|
1052
|
+
changeWorkingDirectory: options.changeWorkingDirectory,
|
|
1053
|
+
cwd: options.cwd,
|
|
1054
|
+
env: options.env,
|
|
1055
|
+
...options.exitAfterSignal !== void 0 ? { exitAfterSignal: options.exitAfterSignal } : {},
|
|
1056
|
+
homeApi: createNodeRivusHome({ deploymentInspector: createRivusHomeDeploymentInspector({ packageManifestPath }) }),
|
|
1057
|
+
homeDirectory: homedir(),
|
|
1058
|
+
loadBootstrap: (specifier) => import(toImportSpecifier(specifier)),
|
|
1059
|
+
nodeExecutable: process.execPath,
|
|
1060
|
+
nodeVersion: process.versions.node,
|
|
1061
|
+
packageManifestPath,
|
|
1062
|
+
signalSource: options.signalSource,
|
|
1063
|
+
stderr: options.stderr,
|
|
1064
|
+
stdout: options.stdout,
|
|
1065
|
+
templateDirectory: join(options.packageDirectory, "examples")
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
function toImportSpecifier(specifier) {
|
|
1069
|
+
if (specifier.startsWith(".") || specifier.startsWith("/")) return pathToFileURL(resolve(specifier)).href;
|
|
1070
|
+
return specifier;
|
|
1009
1071
|
}
|
|
1010
1072
|
//#endregion
|
|
1011
1073
|
//#region src/cli.ts
|
|
1012
1074
|
const packageDirectory = fileURLToPath(new URL("..", import.meta.url));
|
|
1013
|
-
const exitCode = await Effect.runPromise(
|
|
1075
|
+
const exitCode = await Effect.runPromise(runRivusNodeEntrypoint({
|
|
1014
1076
|
argv: process.argv.slice(2),
|
|
1015
1077
|
changeWorkingDirectory: (directory) => process.chdir(directory),
|
|
1016
1078
|
cwd: process.cwd(),
|
|
1017
1079
|
env: process.env,
|
|
1018
1080
|
exitAfterSignal: (code) => process.exit(code),
|
|
1019
|
-
|
|
1020
|
-
loadBootstrap: (specifier) => import(toImportSpecifier(specifier)),
|
|
1021
|
-
homeDirectory: homedir(),
|
|
1022
|
-
nodeExecutable: process.execPath,
|
|
1023
|
-
nodeVersion: process.versions.node,
|
|
1024
|
-
packageManifestPath: join(packageDirectory, "package.json"),
|
|
1081
|
+
packageDirectory,
|
|
1025
1082
|
signalSource: process,
|
|
1026
1083
|
stderr: process.stderr,
|
|
1027
|
-
stdout: process.stdout
|
|
1028
|
-
templateDirectory: join(packageDirectory, "examples")
|
|
1084
|
+
stdout: process.stdout
|
|
1029
1085
|
}));
|
|
1030
1086
|
process.exitCode = exitCode;
|
|
1031
|
-
function toImportSpecifier(specifier) {
|
|
1032
|
-
if (specifier.startsWith(".") || specifier.startsWith("/")) return pathToFileURL(resolve(specifier)).href;
|
|
1033
|
-
return specifier;
|
|
1034
|
-
}
|
|
1035
1087
|
//#endregion
|
|
1036
1088
|
export {};
|