@rivus/agent 0.14.3 → 0.15.0

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