@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.
Files changed (46) hide show
  1. package/README.md +17 -18
  2. package/dist/acp.d.ts +40 -40
  3. package/dist/acp.js +71 -31
  4. package/dist/bootstrap/pi-feishu.d.ts +20 -0
  5. package/dist/bootstrap/pi-feishu.js +596 -0
  6. package/dist/{agent-loop.d.ts → chunks/agent-loop.d.ts} +293 -44
  7. package/dist/chunks/agent-loop.js +1272 -0
  8. package/dist/chunks/api.d.ts +70 -0
  9. package/dist/chunks/api.js +471 -0
  10. package/dist/{rivus-plugin.d.ts → chunks/api2.d.ts} +271 -120
  11. package/dist/chunks/api2.js +1331 -0
  12. package/dist/chunks/api3.d.ts +402 -0
  13. package/dist/chunks/index.d.ts +3662 -0
  14. package/dist/chunks/module.js +267 -0
  15. package/dist/chunks/pi-skill-tool.js +460 -0
  16. package/dist/chunks/pi-tool-proxy.d.ts +188 -0
  17. package/dist/chunks/pi.js +329 -0
  18. package/dist/{rivus-daemon-cli.js → chunks/rivus-daemon-cli.js} +2197 -1526
  19. package/dist/{rivus-plugin-testkit.d.ts → chunks/rivus-plugin-testkit.d.ts} +1 -1
  20. package/dist/{rivus-plugin-testkit.js → chunks/rivus-plugin-testkit.js} +11 -3
  21. package/dist/chunks/sha256-digest.js +12 -0
  22. package/dist/chunks/spi.d.ts +1 -0
  23. package/dist/chunks/spi.js +2 -0
  24. package/dist/chunks/src.js +9897 -0
  25. package/dist/cli.js +604 -95
  26. package/dist/index.d.ts +8 -3645
  27. package/dist/index.js +9 -10483
  28. package/dist/mcp.d.ts +3 -38
  29. package/dist/mcp.js +4 -114
  30. package/dist/pi.d.ts +95 -9
  31. package/dist/pi.js +3 -146
  32. package/dist/testing/index.d.ts +1 -1
  33. package/dist/testing/index.js +1 -1
  34. package/examples/pi-feishu-deployment.bootstrap.ts +45 -54
  35. package/examples/pi-feishu.bootstrap.ts +53 -37
  36. package/examples/rivus-starter.plugin.mjs +3 -1
  37. package/package.json +12 -14
  38. package/dist/agent-loop.js +0 -121
  39. package/dist/agent-memory.d.ts +0 -100
  40. package/dist/agent-memory.js +0 -114
  41. package/dist/background-session-authority.js +0 -224
  42. package/dist/background-session-input.js +0 -45
  43. package/dist/background-session-service.d.ts +0 -291
  44. package/dist/pi-tool-proxy.d.ts +0 -197
  45. package/dist/rivus-plugin-registry.js +0 -215
  46. package/dist/tool-input-digest.js +0 -128
package/dist/cli.js CHANGED
@@ -1,10 +1,130 @@
1
1
  #!/usr/bin/env node
2
- import { $ as resolveFeishuEndpointCredentials, D as loadRivusDeploymentManifest, T as resolveNodeRivusPluginModulePath, et as loadMergedLocalEnvFile, rt as validateRivusDeploymentManifest, t as runRivusDaemonCli } from "./rivus-daemon-cli.js";
3
- import { Effect } from "effect";
4
- import { lstat, mkdir, readFile, rmdir, stat, unlink, writeFile } from "node:fs/promises";
2
+ import { T as loadRivusDeploymentManifest, V as resolveFeishuEndpointCredentials, W as loadMergedLocalEnvFile, d as validateTrustedModulePath, f as isPathWithin, j as validateRivusDeploymentManifest, p as findTrustedPackageRoot, t as runRivusDaemonCli, u as resolveNodeRivusPluginModulePath } from "./chunks/rivus-daemon-cli.js";
3
+ import { Effect, Either } from "effect";
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";
6
+ import { homedir } from "node:os";
6
7
  import { fileURLToPath, pathToFileURL } from "node:url";
7
- //#region src/infrastructure/project/rivus-project-doctor.ts
8
+ //#region src/adapters/deployment/inspection/rivus-home-deployment-inspector.ts
9
+ const DEFAULT_BOOTSTRAP_PEERS = Object.freeze(["@earendil-works/pi-coding-agent", "@larksuiteoapi/node-sdk"]);
10
+ function createRivusHomeDeploymentInspector(options = {}) {
11
+ const resolveModule = options.resolveModule ?? ((specifier) => defaultResolveModule(specifier, options.packageManifestPath));
12
+ const resolvePluginModule = options.resolvePluginModule ?? ((request) => resolveNodeRivusPluginModulePath(request, options.packageManifestPath ? { packageManifestPath: options.packageManifestPath } : {}));
13
+ return { inspect: (input) => Effect.gen(function* () {
14
+ const manifestResult = yield* checkManifest$1(input.home);
15
+ const credentials = yield* checkCredentials$1(input.home, manifestResult.manifest, input.env, input.envFilePath);
16
+ const modules = yield* checkModules(input.home, manifestResult.manifest, resolveModule, resolvePluginModule);
17
+ return {
18
+ credentials,
19
+ manifest: manifestResult.check,
20
+ modules
21
+ };
22
+ }) };
23
+ }
24
+ function checkManifest$1(home) {
25
+ return loadRivusDeploymentManifest(home.manifestPath).pipe(Effect.map((manifest) => {
26
+ validateRivusDeploymentManifest(manifest);
27
+ return {
28
+ check: {
29
+ message: "deployment manifest is valid",
30
+ name: "manifest",
31
+ status: "pass"
32
+ },
33
+ manifest
34
+ };
35
+ }), Effect.catchAll((error) => Effect.succeed({ check: {
36
+ message: `deployment manifest is invalid: ${formatError$1(error)}`,
37
+ name: "manifest",
38
+ status: "fail"
39
+ } })));
40
+ }
41
+ function checkModules(home, manifest, resolveModule, resolvePluginModule) {
42
+ if (!manifest) return Effect.succeed({
43
+ message: "modules cannot be checked until manifest is valid",
44
+ name: "modules",
45
+ status: "fail"
46
+ });
47
+ const specifiers = [home.bootstrap, ...home.bootstrap === "@rivus/agent/bootstrap/pi-feishu" ? DEFAULT_BOOTSTRAP_PEERS : []];
48
+ return Effect.gen(function* () {
49
+ const missing = [];
50
+ for (const specifier of specifiers) {
51
+ const result = yield* toEffect(() => resolveModule(specifier)).pipe(Effect.either);
52
+ if (result._tag === "Left") missing.push(`${specifier} (${formatError$1(result.left)})`);
53
+ }
54
+ for (const plugin of manifest.plugins) {
55
+ const result = yield* toEffect(() => resolvePluginModule({
56
+ deploymentRoot: home.directory,
57
+ module: plugin.module,
58
+ pluginId: plugin.id
59
+ })).pipe(Effect.either);
60
+ if (result._tag === "Left") missing.push(`${plugin.module} (${formatError$1(result.left)})`);
61
+ }
62
+ return missing.length === 0 ? {
63
+ message: "Bootstrap, Plugin, Pi, and Feishu modules resolve from the global installation",
64
+ name: "modules",
65
+ status: "pass"
66
+ } : {
67
+ message: `global npm modules are unavailable: ${missing.join(", ")}`,
68
+ name: "modules",
69
+ status: "fail"
70
+ };
71
+ });
72
+ }
73
+ function toEffect(read) {
74
+ return Effect.suspend(() => {
75
+ try {
76
+ const value = read();
77
+ if (Effect.isEffect(value)) return value;
78
+ return Effect.tryPromise({
79
+ try: () => Promise.resolve(value),
80
+ catch: toError$1
81
+ });
82
+ } catch (error) {
83
+ return Effect.fail(toError$1(error));
84
+ }
85
+ });
86
+ }
87
+ function checkCredentials$1(home, manifest, env, envFilePath) {
88
+ if (!manifest) return Effect.succeed({
89
+ message: "credentials cannot be checked until manifest is valid",
90
+ name: "credentials",
91
+ status: "fail"
92
+ });
93
+ return Effect.tryPromise({
94
+ try: async () => {
95
+ const merged = await loadMergedLocalEnvFile(envFilePath ? resolve(home.directory, envFilePath) : home.envFilePath, env);
96
+ for (const endpoint of manifest.endpoints.filter(({ enabled }) => enabled)) resolveFeishuEndpointCredentials(endpoint.credentialRef, merged);
97
+ return {
98
+ message: "enabled Endpoint credential references resolve",
99
+ name: "credentials",
100
+ status: "pass"
101
+ };
102
+ },
103
+ catch: toError$1
104
+ }).pipe(Effect.catchAll((error) => Effect.succeed({
105
+ message: `enabled Endpoint credentials are incomplete: ${formatError$1(error)}`,
106
+ name: "credentials",
107
+ status: "fail"
108
+ })));
109
+ }
110
+ function defaultResolveModule(specifier, packageManifestPath) {
111
+ if (!packageManifestPath) return Effect.fail(/* @__PURE__ */ new Error("Rivus package manifest path is required for trusted module resolution"));
112
+ return Effect.gen(function* () {
113
+ const resolvedRealpath = yield* Effect.tryPromise({
114
+ try: () => realpath(fileURLToPath(import.meta.resolve(specifier))),
115
+ catch: toError$1
116
+ });
117
+ return yield* validateTrustedModulePath(yield* findTrustedPackageRoot(packageManifestPath), resolvedRealpath, `module ${specifier}`);
118
+ });
119
+ }
120
+ function toError$1(error) {
121
+ return error instanceof Error ? error : new Error(String(error));
122
+ }
123
+ function formatError$1(error) {
124
+ return error instanceof Error ? error.message : String(error);
125
+ }
126
+ //#endregion
127
+ //#region src/adapters/deployment/inspection/rivus-project-doctor.ts
8
128
  const REQUIRED_FILES = Object.freeze([
9
129
  "package.json",
10
130
  "rivus.bootstrap.ts",
@@ -15,23 +135,33 @@ const REQUIRED_DEPENDENCIES = Object.freeze([
15
135
  "@earendil-works/pi-coding-agent",
16
136
  "@larksuiteoapi/node-sdk"
17
137
  ]);
18
- async function diagnoseRivusProject(options) {
19
- const directory = resolve(options.directory);
20
- const checks = [];
21
- checks.push(checkNode(options.nodeVersion));
22
- checks.push(await checkFiles(directory));
23
- checks.push(await checkDependencies(directory));
24
- const manifestResult = await checkManifest(directory);
25
- checks.push(manifestResult.check);
26
- const envFilePath = resolve(directory, options.envFilePath ?? ".env.local");
27
- checks.push(await checkCredentials(manifestResult.manifest, envFilePath, options.env));
28
- return Object.freeze({
29
- checks: Object.freeze(checks),
30
- directory,
31
- ready: checks.every(({ status }) => status === "pass")
138
+ function diagnoseRivusProject(options) {
139
+ return Effect.gen(function* () {
140
+ const directory = resolve(options.directory);
141
+ const checks = [checkNode$1(options.nodeVersion)];
142
+ checks.push(yield* Effect.tryPromise({
143
+ try: () => checkFiles(directory),
144
+ catch: toError
145
+ }));
146
+ checks.push(yield* Effect.tryPromise({
147
+ try: () => checkDependencies(directory),
148
+ catch: toError
149
+ }));
150
+ const manifestResult = yield* checkManifest(directory);
151
+ checks.push(manifestResult.check);
152
+ const envFilePath = resolve(directory, options.envFilePath ?? ".env.local");
153
+ checks.push(yield* Effect.tryPromise({
154
+ try: () => checkCredentials(manifestResult.manifest, envFilePath, options.env),
155
+ catch: toError
156
+ }));
157
+ return Object.freeze({
158
+ checks: Object.freeze(checks),
159
+ directory,
160
+ ready: checks.every(({ status }) => status === "pass")
161
+ });
32
162
  });
33
163
  }
34
- function checkNode(version) {
164
+ function checkNode$1(version) {
35
165
  const [major, minor] = version.split(".").map(Number);
36
166
  if (major === 24 && Number.isInteger(minor) && minor >= 11) return {
37
167
  message: `Node.js ${version} satisfies the supported ^24.11 runtime`,
@@ -70,19 +200,17 @@ async function checkDependencies(directory) {
70
200
  status: "fail"
71
201
  };
72
202
  }
73
- async function checkManifest(directory) {
74
- try {
75
- const manifest = await loadRivusDeploymentManifest(join(directory, "rivus.config.json"));
203
+ function checkManifest(directory) {
204
+ return loadRivusDeploymentManifest(join(directory, "rivus.config.json")).pipe(Effect.flatMap((manifest) => Effect.gen(function* () {
76
205
  validateRivusDeploymentManifest(manifest);
77
206
  const missingModules = [];
78
- for (const plugin of manifest.plugins) try {
79
- await resolveNodeRivusPluginModulePath({
207
+ for (const plugin of manifest.plugins) {
208
+ const result = yield* resolveNodeRivusPluginModulePath({
80
209
  deploymentRoot: directory,
81
210
  module: plugin.module,
82
211
  pluginId: plugin.id
83
- });
84
- } catch {
85
- missingModules.push(plugin.module);
212
+ }).pipe(Effect.either);
213
+ if (Either.isLeft(result)) missingModules.push(plugin.module);
86
214
  }
87
215
  if (missingModules.length > 0) return { check: {
88
216
  message: `manifest Plugin modules are missing: ${missingModules.join(", ")}`,
@@ -97,13 +225,11 @@ async function checkManifest(directory) {
97
225
  },
98
226
  manifest
99
227
  };
100
- } catch (error) {
101
- return { check: {
102
- message: `deployment manifest is invalid: ${error instanceof Error ? error.message : String(error)}`,
103
- name: "manifest",
104
- status: "fail"
105
- } };
106
- }
228
+ })), Effect.catchAll((error) => Effect.succeed({ check: {
229
+ message: `deployment manifest is invalid: ${error.message}`,
230
+ name: "manifest",
231
+ status: "fail"
232
+ } })));
107
233
  }
108
234
  async function checkCredentials(manifest, envFilePath, env) {
109
235
  let mergedEnv;
@@ -149,8 +275,11 @@ async function missingRegularFiles(directory, paths) {
149
275
  function isMissingPath$1(error) {
150
276
  return error instanceof Error && "code" in error && error.code === "ENOENT";
151
277
  }
278
+ function toError(error) {
279
+ return error instanceof Error ? error : new Error(String(error));
280
+ }
152
281
  //#endregion
153
- //#region src/infrastructure/project/rivus-project-initializer.ts
282
+ //#region src/platform/project/setup/rivus-project-initializer.ts
154
283
  const TEMPLATE_FILES = Object.freeze({
155
284
  "current-weather.mjs": "current-weather.mjs",
156
285
  "https-response-reader.mjs": "https-response-reader.mjs",
@@ -161,12 +290,12 @@ async function initializeRivusProject(options) {
161
290
  const directory = resolve(options.directory);
162
291
  const manifest = await readPackageManifest(options.packageManifestPath);
163
292
  const files = /* @__PURE__ */ new Map([
164
- [".env.example", environmentTemplate()],
293
+ [".env.example", environmentTemplate$1()],
165
294
  [".gitignore", ".env.local\n.rivus/\nnode_modules/\n"],
166
295
  ["deploy/launchd/com.rivus.agent.plist", launchdService(directory, options.nodeExecutable)],
167
296
  ["deploy/systemd/rivus.service", systemdService(directory, options.nodeExecutable)],
168
297
  ["package.json", projectPackageJson(directory, manifest)],
169
- ["rivus.config.json", deploymentManifest()]
298
+ ["rivus.config.json", deploymentManifest$1()]
170
299
  ]);
171
300
  for (const [target, source] of Object.entries(TEMPLATE_FILES)) files.set(target, await readFile(join(options.templateDirectory, source), "utf8"));
172
301
  const paths = [...files.keys()].sort();
@@ -231,7 +360,7 @@ async function ensureDirectory(path, projectRoot, createdDirectories) {
231
360
  if (!isAlreadyExists(error)) throw error;
232
361
  const state = await lstat(path);
233
362
  if (state.isSymbolicLink()) {
234
- if (isWithin(projectRoot, path)) throw new Error(`Rivus project initialization refused; symbolic link ancestor: ${relative(projectRoot, path) || "."}`);
363
+ if (isPathWithin(projectRoot, path)) throw new Error(`Rivus project initialization refused; symbolic link ancestor: ${relative(projectRoot, path) || "."}`);
235
364
  if ((await stat(path)).isDirectory()) return;
236
365
  }
237
366
  if (!state.isDirectory()) throw new Error(`Rivus project initialization refused; non-directory ancestor: ${path}`);
@@ -242,12 +371,12 @@ async function rollbackCreatedPaths(files, directories) {
242
371
  for (const path of files.reverse()) try {
243
372
  await unlink(path);
244
373
  } catch (error) {
245
- if (!isMissingPath(error)) errors.push(asError(error));
374
+ if (!isMissingPath(error)) errors.push(asError$1(error));
246
375
  }
247
376
  for (const path of directories.reverse()) try {
248
377
  await rmdir(path);
249
378
  } catch (error) {
250
- if (!isMissingPath(error) && !isDirectoryNotEmpty(error)) errors.push(asError(error));
379
+ if (!isMissingPath(error) && !isDirectoryNotEmpty(error)) errors.push(asError$1(error));
251
380
  }
252
381
  return errors;
253
382
  }
@@ -259,17 +388,13 @@ async function lstatOrUndefined(path) {
259
388
  throw error;
260
389
  }
261
390
  }
262
- function isWithin(root, candidate) {
263
- const child = relative(root, candidate);
264
- return child === "" || !child.startsWith(`..${sep}`) && child !== ".." && !isAbsolute(child);
265
- }
266
391
  function isAlreadyExists(error) {
267
392
  return error instanceof Error && "code" in error && error.code === "EEXIST";
268
393
  }
269
394
  function isDirectoryNotEmpty(error) {
270
395
  return error instanceof Error && "code" in error && error.code === "ENOTEMPTY";
271
396
  }
272
- function asError(error) {
397
+ function asError$1(error) {
273
398
  return error instanceof Error ? error : new Error(String(error));
274
399
  }
275
400
  async function readPackageManifest(path) {
@@ -291,8 +416,8 @@ function isMissingPath(error) {
291
416
  return error instanceof Error && "code" in error && error.code === "ENOENT";
292
417
  }
293
418
  function projectPackageJson(directory, manifest) {
294
- const pi = manifest.peerDependencies?.["@earendil-works/pi-coding-agent"];
295
- const lark = manifest.peerDependencies?.["@larksuiteoapi/node-sdk"];
419
+ const pi = manifest.dependencies?.["@earendil-works/pi-coding-agent"];
420
+ const lark = manifest.dependencies?.["@larksuiteoapi/node-sdk"];
296
421
  const effect = manifest.dependencies?.effect;
297
422
  if (!pi || !lark || !effect) throw new Error("Rivus package manifest is missing its Effect, Pi, or Feishu dependency range");
298
423
  const projectName = sanitizePackageName(basename(directory));
@@ -316,7 +441,7 @@ function projectPackageJson(directory, manifest) {
316
441
  function sanitizePackageName(value) {
317
442
  return value.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[._-]+|[._-]+$/g, "") || "rivus-app";
318
443
  }
319
- function deploymentManifest() {
444
+ function deploymentManifest$1() {
320
445
  return `${JSON.stringify({
321
446
  plugins: [{
322
447
  id: "rivus-starter",
@@ -349,7 +474,7 @@ function deploymentManifest() {
349
474
  }]
350
475
  }, null, 2)}\n`;
351
476
  }
352
- function environmentTemplate() {
477
+ function environmentTemplate$1() {
353
478
  return [
354
479
  "# Copy this file to .env.local and keep the real values untracked.",
355
480
  "RIVUS_FEISHU_APP_ID=",
@@ -393,18 +518,31 @@ function xmlEscape(value) {
393
518
  //#endregion
394
519
  //#region src/composition/rivus-cli.ts
395
520
  const USAGE = `Usage:
521
+ rivus setup [directory]
522
+ rivus start
523
+ rivus status
524
+ rivus check-config
396
525
  rivus init [directory]
397
526
  rivus doctor [directory] [--env-file <path>]
398
527
  rivus --bootstrap <module> [--manifest <rivus.config.json>] [options]
399
528
 
400
529
  Commands:
530
+ setup Create Rivus Home (default ~/.rivus-agent) without overwriting files
531
+ start Start the Rivus Home deployment in the foreground
532
+ status Print Rivus Home deployment status without activating Endpoints
533
+ check-config
534
+ Validate and print the redacted Rivus Home manifest
401
535
  init Create a standalone local Rivus project without overwriting files
402
- doctor Check Node, files, dependencies, manifest, and enabled Endpoint credentials
536
+ doctor Check Rivus Home by default, or an explicit standalone project directory
403
537
 
404
538
  Run rivus --help for the complete daemon option list.
405
539
  `;
406
540
  function runRivusCli(options) {
407
541
  const command = options.argv[0];
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"]);
408
546
  if (command === "init") return runInitCommand(options);
409
547
  if (command === "doctor") return runDoctorCommand(options);
410
548
  if (command && !command.startsWith("-")) return Effect.sync(() => {
@@ -413,60 +551,107 @@ function runRivusCli(options) {
413
551
  });
414
552
  return runRivusDaemonCli(options);
415
553
  }
554
+ function runSetupCommand(options) {
555
+ return withOptionalDirectoryArgument(options, "setup", (argument) => resolveHomeDirectoryEffect(options, argument).pipe(Effect.flatMap((directory) => options.homeApi.setup(directory).pipe(Effect.tap(() => Effect.sync(() => options.stdout.write(`Initialized Rivus Home in ${directory}\n\nNext:\n cp ${shellQuote(`${directory}/.env.example`)} ${shellQuote(`${directory}/.env`)}\n rivus doctor\n rivus start\n`))), Effect.as(0))), Effect.catchAll((error) => writeError(options, error))));
556
+ }
557
+ function runHomeDaemonCommand(options, daemonArgs) {
558
+ if (options.argv.length > 1) return Effect.sync(() => {
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)));
580
+ }
416
581
  function runInitCommand(options) {
417
- return Effect.promise(async () => {
418
- const args = options.argv.slice(1);
419
- if (args.includes("--help") || args.includes("-h")) {
420
- options.stdout.write("Usage: rivus init [directory]\n");
421
- return 0;
422
- }
423
- if (args.length > 1 || args[0]?.startsWith("-")) {
424
- options.stderr.write("Usage: rivus init [directory]\n");
425
- return 1;
426
- }
427
- const directory = resolve(options.cwd, args[0] ?? ".");
428
- try {
429
- const result = await initializeRivusProject({
582
+ return withOptionalDirectoryArgument(options, "init", (argument) => {
583
+ const directory = resolve(options.cwd, argument ?? ".");
584
+ return Effect.tryPromise({
585
+ try: () => initializeRivusProject({
430
586
  directory,
431
587
  nodeExecutable: options.nodeExecutable,
432
588
  packageManifestPath: options.packageManifestPath,
433
589
  templateDirectory: options.templateDirectory
434
- });
435
- 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`);
436
- return 0;
437
- } catch (error) {
438
- options.stderr.write(`${formatError(error)}\n`);
439
- return 1;
440
- }
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)));
441
593
  });
442
594
  }
595
+ function withOptionalDirectoryArgument(options, command, run) {
596
+ const args = options.argv.slice(1);
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]);
607
+ }
443
608
  function runDoctorCommand(options) {
444
- return Effect.promise(async () => {
445
- const parsed = parseDoctorArguments(options.argv.slice(1));
446
- if (parsed.help) {
447
- options.stdout.write("Usage: rivus doctor [directory] [--env-file <path>]\n");
448
- return 0;
449
- }
450
- if (parsed.error) {
451
- options.stderr.write(`${parsed.error}\nUsage: rivus doctor [directory] [--env-file <path>]\n`);
452
- return 1;
453
- }
454
- try {
455
- const report = await diagnoseRivusProject({
456
- directory: resolve(options.cwd, parsed.directory ?? "."),
457
- env: options.env,
458
- ...parsed.envFilePath ? { envFilePath: parsed.envFilePath } : {},
459
- nodeVersion: options.nodeVersion
460
- });
461
- options.stdout.write(`Rivus doctor: ${report.directory}\n`);
462
- for (const check of report.checks) options.stdout.write(`${check.status === "pass" ? "PASS" : "FAIL"} ${check.name}: ${check.message}\n`);
463
- options.stdout.write(report.ready ? "Rivus project is ready\n" : "Rivus project is not ready\n");
464
- return report.ready ? 0 : 1;
465
- } catch (error) {
466
- options.stderr.write(`${formatError(error)}\n`);
467
- return 1;
468
- }
609
+ const parsed = parseDoctorArguments(options.argv.slice(1));
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;
469
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)));
631
+ }
632
+ function resolveHomeDirectoryEffect(options, argument) {
633
+ return Effect.try({
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
+ });
643
+ }
644
+ function writeError(options, error) {
645
+ return Effect.sync(() => {
646
+ options.stderr.write(`${formatError(error)}\n`);
647
+ return 1;
648
+ });
649
+ }
650
+ function writeDoctorReport(stdout, report, owner) {
651
+ stdout.write(`Rivus doctor: ${report.directory}\n`);
652
+ for (const check of report.checks) stdout.write(`${check.status === "pass" ? "PASS" : "FAIL"} ${check.name}: ${check.message}\n`);
653
+ stdout.write(report.ready ? `Rivus ${owner} is ready\n` : `Rivus ${owner} is not ready\n`);
654
+ return report.ready ? 0 : 1;
470
655
  }
471
656
  function parseDoctorArguments(argv) {
472
657
  let directory;
@@ -502,14 +687,338 @@ function shellQuote(value) {
502
687
  return `'${value.replaceAll("'", "'\\''")}'`;
503
688
  }
504
689
  //#endregion
690
+ //#region src/platform/home/config/rivus-home-config.ts
691
+ /** Loads and validates the operator-owned Home config file. */
692
+ function loadRivusHome(directoryPath) {
693
+ const directory = resolve(directoryPath);
694
+ const configPath = resolve(directory, "config.json");
695
+ return Effect.tryPromise({
696
+ try: async () => {
697
+ const config = parseRivusHomeConfig(JSON.parse(await readFile(configPath, "utf8")));
698
+ return {
699
+ bootstrap: resolveBootstrap(directory, config.bootstrap),
700
+ config,
701
+ configPath,
702
+ directory,
703
+ envFilePath: resolveHomePath(directory, config.envFile, "envFile"),
704
+ logsDirectory: resolveHomePath(directory, config.logs, "logs"),
705
+ manifestPath: resolveHomePath(directory, config.manifest, "manifest"),
706
+ stateDirectory: resolveHomePath(directory, config.state, "state"),
707
+ workspaceDirectory: resolveHomePath(directory, config.workspace, "workspace")
708
+ };
709
+ },
710
+ catch: asError
711
+ });
712
+ }
713
+ function parseRivusHomeConfig(value) {
714
+ const config = record(value, "Rivus Home config");
715
+ if (config.version !== 1) throw new Error("Rivus Home config version must be 1");
716
+ return {
717
+ bootstrap: nonEmptyString(config.bootstrap, "bootstrap"),
718
+ envFile: relativePath(config.envFile, "envFile"),
719
+ logs: relativePath(config.logs, "logs"),
720
+ manifest: relativePath(config.manifest, "manifest"),
721
+ state: relativePath(config.state, "state"),
722
+ version: 1,
723
+ workspace: relativePath(config.workspace, "workspace")
724
+ };
725
+ }
726
+ function resolveBootstrap(directory, value) {
727
+ if (value.startsWith("./") || value.startsWith("../")) return resolveHomePath(directory, value, "bootstrap");
728
+ if (isAbsolute(value)) throw new Error("Rivus Home bootstrap must be a package specifier or a relative path inside Rivus Home");
729
+ return value;
730
+ }
731
+ function resolveHomePath(directory, value, owner) {
732
+ const candidate = resolve(directory, value);
733
+ const relation = relative(directory, candidate);
734
+ if (relation === "" || !relation.startsWith(`..${sep}`) && relation !== ".." && !isAbsolute(relation)) return candidate;
735
+ throw new Error(`Rivus Home ${owner} escapes the Home directory`);
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;
745
+ }
746
+ function record(value, owner) {
747
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${owner} must be an object`);
748
+ return value;
749
+ }
750
+ function asError(error) {
751
+ return error instanceof Error ? error : new Error(String(error));
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",
788
+ status: "pass"
789
+ } : {
790
+ message: `Node.js 24.11 or newer on major 24 is required; current version is ${version}`,
791
+ name: "node",
792
+ status: "fail"
793
+ };
794
+ }
795
+ function checkWorkspace(home, findMissingWorkspacePaths) {
796
+ return findMissingWorkspacePaths(home).pipe(Effect.map((missing) => missing.length === 0 ? {
797
+ message: "Workspace instructions, Memory, Skills, and work directories are present",
798
+ name: "workspace",
799
+ status: "pass"
800
+ } : {
801
+ message: `Workspace paths are missing: ${missing.join(", ")}`,
802
+ name: "workspace",
803
+ status: "fail"
804
+ }));
805
+ }
806
+ function blockedCheck(name, owner) {
807
+ return {
808
+ message: `${owner} cannot be checked until config.json is valid`,
809
+ name,
810
+ status: "fail"
811
+ };
812
+ }
813
+ function report(directory, checks) {
814
+ return {
815
+ checks,
816
+ directory,
817
+ ready: checks.every(({ status }) => status === "pass")
818
+ };
819
+ }
820
+ //#endregion
821
+ //#region src/platform/home/setup/rivus-home-layout.ts
822
+ const HOME_DIRECTORIES = Object.freeze([
823
+ "logs",
824
+ "plugins",
825
+ "state",
826
+ "workspace/memory",
827
+ "workspace/skills",
828
+ "workspace/work/artifacts",
829
+ "workspace/work/drafts",
830
+ "workspace/work/inbox",
831
+ "workspace/work/tmp"
832
+ ]);
833
+ function createRivusHomeLayout() {
834
+ return {
835
+ directories: HOME_DIRECTORIES,
836
+ files: /* @__PURE__ */ new Map([
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
+ ])
853
+ };
854
+ }
855
+ function homeConfig() {
856
+ return `${JSON.stringify({
857
+ bootstrap: "@rivus/agent/bootstrap/pi-feishu",
858
+ envFile: ".env",
859
+ logs: "logs",
860
+ manifest: "rivus.config.json",
861
+ state: "state",
862
+ version: 1,
863
+ workspace: "workspace"
864
+ }, null, 2)}\n`;
865
+ }
866
+ function deploymentManifest() {
867
+ return `${JSON.stringify({
868
+ agents: [{
869
+ agentId: "personal",
870
+ endpointIds: ["personal-feishu"],
871
+ memory: {
872
+ scopes: ["agent-private"],
873
+ tool: true
874
+ },
875
+ pluginId: "rivus-starter",
876
+ 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
+ skills: { allow: [] },
888
+ tools: { allow: ["rivus-starter/current-weather"] }
889
+ }],
890
+ defaultAgentId: "personal",
891
+ defaultEndpointId: "personal-feishu",
892
+ endpoints: [{
893
+ agentId: "personal",
894
+ baseUrl: "https://open.feishu.cn",
895
+ cardStreamLeaseMs: 51e4,
896
+ credentialRef: "env:RIVUS_FEISHU",
897
+ enabled: true,
898
+ experimental: { cotMessages: false },
899
+ groupPolicy: "mention-only",
900
+ id: "personal-feishu",
901
+ progressDisplay: "collapsed",
902
+ required: true,
903
+ sessionNamespace: "rivus-home-v1",
904
+ 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
+ }]
917
+ }, null, 2)}\n`;
918
+ }
919
+ function environmentTemplate() {
920
+ return [
921
+ "# Copy this file to .env and keep the real values untracked.",
922
+ "RIVUS_FEISHU_APP_ID=",
923
+ "RIVUS_FEISHU_APP_SECRET=",
924
+ "PI_MODEL=",
925
+ "PI_API_KEY=",
926
+ "# PI_BASE_URL=",
927
+ "RIVUS_WEATHER_DEFAULT_LOCATION=上海",
928
+ ""
929
+ ].join("\n");
930
+ }
931
+ function agentsTemplate() {
932
+ return [
933
+ "# Personal Workspace",
934
+ "",
935
+ "Use this directory as the default home for personal work.",
936
+ "",
937
+ "- Read `SOUL.md` and `IDENTITY.md` when identity or tone matters.",
938
+ "- Read `USER.md` when stable user preferences affect the task.",
939
+ "- Search `MEMORY.md` and dated files under `memory/` when prior facts or decisions matter.",
940
+ "- Read the selected `skills/<name>/SKILL.md` before following a Workspace Skill.",
941
+ "- Put incoming material in `work/inbox/`, drafts in `work/drafts/`, and durable general outputs in `work/artifacts/`.",
942
+ "- Put disposable files in `work/tmp/`.",
943
+ "- Store only confirmed durable facts in Memory; keep credentials and raw private transcripts out of tracked files.",
944
+ ""
945
+ ].join("\n");
946
+ }
947
+ //#endregion
948
+ //#region src/platform/home/setup/rivus-home-initializer.ts
949
+ function initializeRivusHome(directoryPath) {
950
+ const directory = resolve(directoryPath);
951
+ const { directories, files } = createRivusHomeLayout();
952
+ const paths = [...files.keys()].sort();
953
+ return Effect.tryPromise({
954
+ try: async () => {
955
+ for (const path of [...directories].sort()) await mkdir(join(directory, path), { recursive: true });
956
+ for (const path of paths) {
957
+ const destination = join(directory, path);
958
+ await mkdir(dirname(destination), { recursive: true });
959
+ await writeFile(destination, files.get(path), {
960
+ encoding: "utf8",
961
+ flag: "wx"
962
+ });
963
+ }
964
+ return {
965
+ directory,
966
+ files: paths
967
+ };
968
+ },
969
+ catch: (error) => error instanceof Error ? error : new Error(String(error))
970
+ });
971
+ }
972
+ //#endregion
973
+ //#region src/platform/home/workspace/rivus-home-workspace.ts
974
+ function findMissingRivusHomeWorkspacePaths(home) {
975
+ const required = [
976
+ home.workspaceDirectory,
977
+ resolve(home.workspaceDirectory, "AGENTS.md"),
978
+ resolve(home.workspaceDirectory, "MEMORY.md"),
979
+ resolve(home.workspaceDirectory, "memory"),
980
+ resolve(home.workspaceDirectory, "skills"),
981
+ resolve(home.workspaceDirectory, "work")
982
+ ];
983
+ return Effect.tryPromise({
984
+ try: async () => {
985
+ const missing = [];
986
+ for (const path of required) try {
987
+ await stat(path);
988
+ } catch (error) {
989
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") missing.push(path);
990
+ else throw error;
991
+ }
992
+ return missing;
993
+ },
994
+ catch: (error) => error instanceof Error ? error : new Error(String(error))
995
+ });
996
+ }
997
+ //#endregion
998
+ //#region src/platform/home/node/node-rivus-home.ts
999
+ function createNodeRivusHome(options) {
1000
+ return {
1001
+ diagnose: (input) => diagnoseRivusHome(input, {
1002
+ deploymentInspector: options.deploymentInspector,
1003
+ findMissingWorkspacePaths: findMissingRivusHomeWorkspacePaths,
1004
+ load: loadRivusHome
1005
+ }),
1006
+ load: loadRivusHome,
1007
+ setup: initializeRivusHome
1008
+ };
1009
+ }
1010
+ //#endregion
505
1011
  //#region src/cli.ts
506
1012
  const packageDirectory = fileURLToPath(new URL("..", import.meta.url));
507
1013
  const exitCode = await Effect.runPromise(runRivusCli({
508
1014
  argv: process.argv.slice(2),
1015
+ changeWorkingDirectory: (directory) => process.chdir(directory),
509
1016
  cwd: process.cwd(),
510
1017
  env: process.env,
511
1018
  exitAfterSignal: (code) => process.exit(code),
1019
+ homeApi: createNodeRivusHome({ deploymentInspector: createRivusHomeDeploymentInspector({ packageManifestPath: join(packageDirectory, "package.json") }) }),
512
1020
  loadBootstrap: (specifier) => import(toImportSpecifier(specifier)),
1021
+ homeDirectory: homedir(),
513
1022
  nodeExecutable: process.execPath,
514
1023
  nodeVersion: process.versions.node,
515
1024
  packageManifestPath: join(packageDirectory, "package.json"),