@rivus/agent 0.4.1 → 0.5.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.
@@ -3,8 +3,8 @@ import { createRequire } from "node:module";
3
3
  import { Effect } from "effect";
4
4
  import { createHash, randomUUID } from "node:crypto";
5
5
  import { pathToFileURL } from "node:url";
6
- import { dirname, isAbsolute, join, relative, sep } from "node:path";
7
- import { open, readFile, realpath, stat } from "node:fs/promises";
6
+ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
7
+ import { lstat, open, readFile, readdir, realpath, stat } from "node:fs/promises";
8
8
  import { constants } from "node:fs";
9
9
  //#region src/application/plugin/rivus-automation-runtime-definition.ts
10
10
  function resolveRivusAutomationRuntimeDefinition(definition, requestedToolIds, requestedSkillIds) {
@@ -108,7 +108,12 @@ async function loadRivusDeployment(options) {
108
108
  continue;
109
109
  }
110
110
  try {
111
- const definition = resolveRivusAgentDefinition(catalog, agent);
111
+ const baseDefinition = resolveRivusAgentDefinition(catalog, agent);
112
+ const projectSpace = agent.projectSpaceId ? options.manifest.projectSpaces?.find(({ id }) => id === agent.projectSpaceId) : void 0;
113
+ const definition = projectSpace ? deepFreeze({
114
+ ...baseDefinition,
115
+ projectSpaceRevision: `project-space-declaration:${JSON.stringify(projectSpace)}`
116
+ }) : baseDefinition;
112
117
  definitions.push(definition);
113
118
  agentStatuses.push(Object.freeze({
114
119
  agentId: agent.agentId,
@@ -158,6 +163,20 @@ async function loadRivusDeployment(options) {
158
163
  });
159
164
  }
160
165
  function validateRivusDeploymentManifest(manifest) {
166
+ const projectSpaceIds = /* @__PURE__ */ new Set();
167
+ for (const projectSpace of manifest.projectSpaces ?? []) {
168
+ if (projectSpaceIds.has(projectSpace.id)) throw new Error(`duplicate project space: ${projectSpace.id}`);
169
+ projectSpaceIds.add(projectSpace.id);
170
+ validateRelativeProjectPath(projectSpace.root, `project space ${projectSpace.id} root`);
171
+ validateRelativeProjectPath(projectSpace.workingDirectory, `project space ${projectSpace.id} working directory`);
172
+ if (projectSpace.skills.sources.length === 0) throw new Error(`project space ${projectSpace.id} must declare at least one Skill source`);
173
+ const sources = /* @__PURE__ */ new Set();
174
+ for (const source of projectSpace.skills.sources) {
175
+ validateRelativeProjectPath(source, `project space ${projectSpace.id} Skill source`);
176
+ if (sources.has(source)) throw new Error(`project space ${projectSpace.id} contains duplicate Skill source: ${source}`);
177
+ sources.add(source);
178
+ }
179
+ }
161
180
  const pluginIds = /* @__PURE__ */ new Set();
162
181
  for (const plugin of manifest.plugins) {
163
182
  if (pluginIds.has(plugin.id)) throw new Error(`duplicate plugin declaration: ${plugin.id}`);
@@ -170,6 +189,7 @@ function validateRivusDeploymentManifest(manifest) {
170
189
  if (agentIds.has(agent.agentId)) throw new Error(`duplicate agent deployment: ${agent.agentId}`);
171
190
  agentIds.add(agent.agentId);
172
191
  if (!pluginIds.has(agent.pluginId)) throw new Error(`agent ${agent.agentId} references undeclared plugin: ${agent.pluginId}`);
192
+ if (agent.projectSpaceId && !projectSpaceIds.has(agent.projectSpaceId)) throw new Error(`agent ${agent.agentId} references unknown project space: ${agent.projectSpaceId}`);
173
193
  agentById.set(agent.agentId, agent);
174
194
  }
175
195
  const automationIds = /* @__PURE__ */ new Set();
@@ -204,6 +224,9 @@ function validateRivusDeploymentManifest(manifest) {
204
224
  if (defaultEndpoint.agentId !== defaultAgent.agentId) throw new Error("default endpoint is not bound to the default agent");
205
225
  if (!defaultEndpoint.enabled) throw new Error("default endpoint must be enabled");
206
226
  }
227
+ function validateRelativeProjectPath(value, owner) {
228
+ if (value.trim() === "" || isAbsolute(value) || value.includes("\0")) throw new Error(`${owner} must be a non-empty relative path`);
229
+ }
207
230
  function validateModuleSpecifier(moduleSpecifier) {
208
231
  if (moduleSpecifier.trim() === "" || isAbsolute(moduleSpecifier) || /^[a-z][a-z+.-]*:/i.test(moduleSpecifier) || moduleSpecifier.includes("\0")) throw new Error(`invalid plugin module specifier: ${moduleSpecifier}`);
209
232
  if ((moduleSpecifier.startsWith("./") || moduleSpecifier.startsWith("../")) && moduleSpecifier.split(/[\\/]/).includes("..")) throw new Error(`plugin module escapes deployment root: ${moduleSpecifier}`);
@@ -321,8 +344,9 @@ function parseManifest(value) {
321
344
  "defaultAgentId",
322
345
  "defaultEndpointId",
323
346
  "endpoints",
324
- "plugins"
325
- ], "manifest", ["automations"]);
347
+ "plugins",
348
+ "projectSpaces"
349
+ ], "manifest", ["automations", "projectSpaces"]);
326
350
  const plugins = array(root.plugins, "manifest.plugins").map((entry, index) => {
327
351
  const plugin = record(entry, `manifest.plugins[${index}]`);
328
352
  exactKeys(plugin, [
@@ -344,9 +368,10 @@ function parseManifest(value) {
344
368
  "memory",
345
369
  "pluginId",
346
370
  "profileId",
371
+ "projectSpaceId",
347
372
  "skills",
348
373
  "tools"
349
- ], `manifest.agents[${index}]`, ["memory"]);
374
+ ], `manifest.agents[${index}]`, ["memory", "projectSpaceId"]);
350
375
  const memory = agent.memory === void 0 ? void 0 : record(agent.memory, `manifest.agents[${index}].memory`);
351
376
  if (memory) exactKeys(memory, ["scopes", "tool"], `manifest.agents[${index}].memory`);
352
377
  const skills = record(agent.skills, `manifest.agents[${index}].skills`);
@@ -362,6 +387,7 @@ function parseManifest(value) {
362
387
  }) } : {},
363
388
  pluginId: string(agent.pluginId, `manifest.agents[${index}].pluginId`),
364
389
  profileId: string(agent.profileId, `manifest.agents[${index}].profileId`),
390
+ ...agent.projectSpaceId === void 0 ? {} : { projectSpaceId: string(agent.projectSpaceId, `manifest.agents[${index}].projectSpaceId`) },
365
391
  skills: Object.freeze({ allow: Object.freeze(array(skills.allow, `manifest.agents[${index}].skills.allow`).map((item, itemIndex) => string(item, `manifest.agents[${index}].skills.allow[${itemIndex}]`))) }),
366
392
  tools: Object.freeze({ allow: Object.freeze(array(tools.allow, `manifest.agents[${index}].tools.allow`).map((item, itemIndex) => string(item, `manifest.agents[${index}].tools.allow[${itemIndex}]`))) })
367
393
  });
@@ -428,13 +454,31 @@ function parseManifest(value) {
428
454
  timeZone: string(automation.timeZone, `manifest.automations[${index}].timeZone`)
429
455
  });
430
456
  });
457
+ const projectSpaces = array(root.projectSpaces ?? [], "manifest.projectSpaces").map((entry, index) => {
458
+ const projectSpace = record(entry, `manifest.projectSpaces[${index}]`);
459
+ exactKeys(projectSpace, [
460
+ "id",
461
+ "root",
462
+ "skills",
463
+ "workingDirectory"
464
+ ], `manifest.projectSpaces[${index}]`);
465
+ const skills = record(projectSpace.skills, `manifest.projectSpaces[${index}].skills`);
466
+ exactKeys(skills, ["sources"], `manifest.projectSpaces[${index}].skills`);
467
+ return Object.freeze({
468
+ id: string(projectSpace.id, `manifest.projectSpaces[${index}].id`),
469
+ root: string(projectSpace.root, `manifest.projectSpaces[${index}].root`),
470
+ skills: Object.freeze({ sources: Object.freeze(array(skills.sources, `manifest.projectSpaces[${index}].skills.sources`).map((item, itemIndex) => string(item, `manifest.projectSpaces[${index}].skills.sources[${itemIndex}]`))) }),
471
+ workingDirectory: string(projectSpace.workingDirectory, `manifest.projectSpaces[${index}].workingDirectory`)
472
+ });
473
+ });
431
474
  return Object.freeze({
432
475
  agents: Object.freeze(agents),
433
476
  automations: Object.freeze(automations),
434
477
  defaultAgentId: string(root.defaultAgentId, "manifest.defaultAgentId"),
435
478
  defaultEndpointId: string(root.defaultEndpointId, "manifest.defaultEndpointId"),
436
479
  endpoints: Object.freeze(endpoints),
437
- plugins: Object.freeze(plugins)
480
+ plugins: Object.freeze(plugins),
481
+ projectSpaces: Object.freeze(projectSpaces)
438
482
  });
439
483
  }
440
484
  function record(value, path) {
@@ -458,7 +502,7 @@ function positiveInteger(value, path) {
458
502
  return value;
459
503
  }
460
504
  function memoryScope(value, path) {
461
- if (typeof value !== "string" || !MEMORY_SCOPES.includes(value)) throw new Error(`${path} must be conversation, agent-private, or shared-user-profile`);
505
+ if (typeof value !== "string" || !MEMORY_SCOPES.includes(value)) throw new Error(`${path} must be conversation, agent-private, project, or shared-user-profile`);
462
506
  return value;
463
507
  }
464
508
  function groupPolicy(value, path) {
@@ -796,6 +840,9 @@ function createAgentInstanceRegistry(options = {}) {
796
840
  const runtimeGenerationId = createStableId("generation", {
797
841
  agentId: definition.agentId,
798
842
  profileRevision: definition.profileRevision,
843
+ projectSpaceId: definition.projectSpaceId ?? null,
844
+ projectSpaceRevision: definition.projectSpaceRevision ?? null,
845
+ skillGrantRevision: definition.skillGrantSet.revision,
799
846
  toolGrantRevision: definition.toolGrantSet.revision
800
847
  });
801
848
  const bindingId = binding.kind === "endpoint" ? binding.endpointId : binding.automationId;
@@ -955,16 +1002,26 @@ var RivusDeploymentAutomationReadinessError = class extends Error {
955
1002
  };
956
1003
  async function createRivusDeploymentDaemon(options) {
957
1004
  const deployment = await loadRivusDeployment(options);
1005
+ if ((deployment.manifest.projectSpaces?.length ?? 0) > 0 && !options.resolveProjectSpace) throw new RivusDeploymentDaemonLifecycleError("deployment bootstrap does not provide a Project Space resolver");
1006
+ const projectSpaces = new Map(await Promise.all((deployment.manifest.projectSpaces ?? []).map(async (declaration) => {
1007
+ const projectSpace = await options.resolveProjectSpace({
1008
+ declaration,
1009
+ deploymentRoot: options.deploymentRoot
1010
+ });
1011
+ return [projectSpace.id, projectSpace];
1012
+ })));
958
1013
  const definitions = new Map(deployment.definitions.map((definition) => [definition.agentId, definition]));
959
1014
  const automationDefinitions = new Map(deployment.automationDefinitions.map((definition) => [definition.id, definition]));
960
1015
  const runtimePool = createAgentRuntimePool({
961
1016
  createRuntime: (instance) => {
962
1017
  const definition = instance.binding.kind === "automation" ? automationDefinitions.get(instance.binding.automationId)?.runtimeDefinition : definitions.get(instance.agentId);
963
1018
  if (!definition) throw new RivusDeploymentDaemonLifecycleError(`runtime instance references unknown agent: ${instance.agentId}`);
1019
+ const projectSpace = definition.projectSpaceId ? projectSpaces.get(definition.projectSpaceId) : void 0;
964
1020
  return options.createRuntime({
965
1021
  ...instance,
966
1022
  catalog: deployment.catalog,
967
- definition
1023
+ definition,
1024
+ ...projectSpace ? { projectSpace } : {}
968
1025
  });
969
1026
  },
970
1027
  registry: createAgentInstanceRegistry(options.initialInstanceRecords ? { initialRecords: options.initialInstanceRecords } : {})
@@ -1063,7 +1120,8 @@ async function createRivusDeploymentDaemon(options) {
1063
1120
  definition: slot.definition,
1064
1121
  endpointId: slot.definition.id,
1065
1122
  handle: (input) => handleEndpoint(slot.definition.id, input),
1066
- instanceId: host.resolveEndpoint(slot.definition.id).instanceId
1123
+ instanceId: host.resolveEndpoint(slot.definition.id).instanceId,
1124
+ ...definitions.get(slot.definition.agentId)?.projectSpaceId ? { projectSpaceId: definitions.get(slot.definition.agentId).projectSpaceId } : {}
1067
1125
  }));
1068
1126
  degraded ||= slotDegraded;
1069
1127
  }
@@ -1171,6 +1229,79 @@ async function stopSlots(slots, errors) {
1171
1229
  }
1172
1230
  }
1173
1231
  //#endregion
1232
+ //#region src/domain/project-space.ts
1233
+ var InvalidRivusProjectSpace = class extends Error {
1234
+ name = "InvalidRivusProjectSpace";
1235
+ };
1236
+ //#endregion
1237
+ //#region src/infrastructure/workspace/rivus-project-space.ts
1238
+ async function resolveRivusProjectSpace(input) {
1239
+ const root = await resolveContainedDirectory(await resolveExistingDirectory(input.deploymentRoot, "deployment root"), input.declaration.root, "Project Space root");
1240
+ const workingDirectory = await resolveContainedDirectory(root, input.declaration.workingDirectory, "Project Space working directory");
1241
+ const skillPaths = await Promise.all(input.declaration.skills.sources.map((source) => resolveContainedPath(root, source, "Project Space Skill source")));
1242
+ await Promise.all(skillPaths.map((path) => assertTreeContainsNoSymbolicLinks(path)));
1243
+ return Object.freeze({
1244
+ id: input.declaration.id,
1245
+ revision: createStableId("project-space", {
1246
+ id: input.declaration.id,
1247
+ root,
1248
+ skillPaths,
1249
+ workingDirectory
1250
+ }),
1251
+ root,
1252
+ skillPaths: Object.freeze(skillPaths),
1253
+ workingDirectory
1254
+ });
1255
+ }
1256
+ async function assertTreeContainsNoSymbolicLinks(path) {
1257
+ const metadata = await lstat(path);
1258
+ if (metadata.isSymbolicLink()) throw new InvalidRivusProjectSpace(`Project Space Skill source contains a symbolic link: ${path}`);
1259
+ if (!metadata.isDirectory()) return;
1260
+ for (const entry of await readdir(path, { withFileTypes: true })) {
1261
+ const child = resolve(path, entry.name);
1262
+ if (entry.isSymbolicLink()) throw new InvalidRivusProjectSpace(`Project Space Skill source contains a symbolic link: ${child}`);
1263
+ if (entry.isDirectory()) await assertTreeContainsNoSymbolicLinks(child);
1264
+ }
1265
+ }
1266
+ async function resolveContainedDirectory(base, path, owner) {
1267
+ const resolved = await resolveContainedPath(base, path, owner);
1268
+ if (!(await stat(resolved)).isDirectory()) throw new InvalidRivusProjectSpace(`${owner} must be a directory: ${path}`);
1269
+ return resolved;
1270
+ }
1271
+ async function resolveContainedPath(base, path, owner) {
1272
+ validateRelativePath(path, owner);
1273
+ const candidate = resolve(base, path);
1274
+ assertContained(base, candidate, owner);
1275
+ let resolved;
1276
+ try {
1277
+ resolved = await realpath(candidate);
1278
+ } catch (cause) {
1279
+ throw new InvalidRivusProjectSpace(`${owner} does not exist: ${path}`, { cause });
1280
+ }
1281
+ assertContained(base, resolved, owner);
1282
+ const metadata = await stat(resolved);
1283
+ if (!metadata.isDirectory() && !metadata.isFile()) throw new InvalidRivusProjectSpace(`${owner} must be a file or directory: ${path}`);
1284
+ return resolved;
1285
+ }
1286
+ async function resolveExistingDirectory(path, owner) {
1287
+ let resolved;
1288
+ try {
1289
+ resolved = await realpath(path);
1290
+ } catch (cause) {
1291
+ throw new InvalidRivusProjectSpace(`${owner} does not exist`, { cause });
1292
+ }
1293
+ if (!(await stat(resolved)).isDirectory()) throw new InvalidRivusProjectSpace(`${owner} must be a directory`);
1294
+ return resolved;
1295
+ }
1296
+ function validateRelativePath(path, owner) {
1297
+ if (!path.trim() || path.includes("\0") || isAbsolute(path)) throw new InvalidRivusProjectSpace(`${owner} must be a non-empty relative path`);
1298
+ }
1299
+ function assertContained(base, candidate, owner) {
1300
+ const relation = relative(base, candidate);
1301
+ if (relation === "" || !relation.startsWith("..") && !isAbsolute(relation)) return;
1302
+ throw new InvalidRivusProjectSpace(`${owner} escapes its trusted root`);
1303
+ }
1304
+ //#endregion
1174
1305
  //#region src/infrastructure/runtime/configured-rivus-deployment-daemon.ts
1175
1306
  async function createConfiguredRivusDeploymentDaemon(options) {
1176
1307
  const manifest = await loadRivusDeploymentManifest(options.manifestPath, options.manifestOptions);
@@ -1181,7 +1312,8 @@ async function createConfiguredRivusDeploymentDaemon(options) {
1181
1312
  deploymentRoot: dirname(options.manifestPath),
1182
1313
  ...options.initialInstanceRecords ? { initialInstanceRecords: options.initialInstanceRecords } : {},
1183
1314
  loadModule: loadNodeRivusPluginModule,
1184
- manifest
1315
+ manifest,
1316
+ resolveProjectSpace: (input) => resolveRivusProjectSpace(input)
1185
1317
  });
1186
1318
  }
1187
1319
  //#endregion
@@ -1284,6 +1416,7 @@ async function createRivusDeploymentCliProcess(factory, context) {
1284
1416
  memory: {
1285
1417
  audience: "private",
1286
1418
  ...context.env.RIVUS_LOCAL_CONVERSATION_ID?.trim() ? { conversationId: context.env.RIVUS_LOCAL_CONVERSATION_ID.trim() } : {},
1419
+ ...daemon.deployment.definitions.find(({ agentId }) => agentId === daemon.deployment.manifest.defaultAgentId)?.projectSpaceId ? { projectId: daemon.deployment.definitions.find(({ agentId }) => agentId === daemon.deployment.manifest.defaultAgentId).projectSpaceId } : {},
1287
1420
  subjectId: context.env.RIVUS_LOCAL_SUBJECT_ID?.trim() || "local-operator",
1288
1421
  tenantId: context.env.RIVUS_MEMORY_TENANT_ID?.trim() || "local"
1289
1422
  },
@@ -2627,4 +2760,4 @@ function hasRecoveryRunner(daemon) {
2627
2760
  return typeof daemon.openRecoveryControl === "function";
2628
2761
  }
2629
2762
  //#endregion
2630
- export { loadRivusDeployment as A, resolveNodeRivusPluginModulePath as C, resolveFeishuEndpointCredentials as D, FeishuEndpointCredentialError as E, loadMergedLocalEnvFile as O, loadNodeRivusPluginModule as S, loadRivusDeploymentManifest as T, OpenClawEnvImportError as _, RivusDeploymentDaemonLifecycleError as a, RivusDaemonConfigError as b, InvalidRivusEndpointBinding as c, AgentRuntimeDisposed as d, createAgentRuntimePool as f, createRivusDaemonShutdownController as g, createStableId as h, RivusDeploymentAutomationReadinessError as i, validateRivusDeploymentManifest as j, RivusPluginLoadError as k, createRivusAgentHost as l, createAgentInstanceRegistry as m, createRivusDeploymentCliProcess as n, RivusDeploymentReadinessError as o, AgentInstanceConflict as p, createConfiguredRivusDeploymentDaemon as r, createRivusDeploymentDaemon as s, runRivusDaemonCli as t, AgentInstanceBusy as u, createRivusEnvFromOpenClawConfig as v, RivusDeploymentManifestError as w, loadRivusDaemonConfig as x, formatRivusEnvFile as y };
2763
+ export { loadMergedLocalEnvFile as A, loadRivusDaemonConfig as C, loadRivusDeploymentManifest as D, RivusDeploymentManifestError as E, loadRivusDeployment as M, validateRivusDeploymentManifest as N, FeishuEndpointCredentialError as O, RivusDaemonConfigError as S, resolveNodeRivusPluginModulePath as T, createStableId as _, InvalidRivusProjectSpace as a, createRivusEnvFromOpenClawConfig as b, RivusDeploymentReadinessError as c, createRivusAgentHost as d, AgentInstanceBusy as f, createAgentInstanceRegistry as g, AgentInstanceConflict as h, resolveRivusProjectSpace as i, RivusPluginLoadError as j, resolveFeishuEndpointCredentials as k, createRivusDeploymentDaemon as l, createAgentRuntimePool as m, createRivusDeploymentCliProcess as n, RivusDeploymentAutomationReadinessError as o, AgentRuntimeDisposed as p, createConfiguredRivusDeploymentDaemon as r, RivusDeploymentDaemonLifecycleError as s, runRivusDaemonCli as t, InvalidRivusEndpointBinding as u, createRivusDaemonShutdownController as v, loadNodeRivusPluginModule as w, formatRivusEnvFile as x, OpenClawEnvImportError as y };
@@ -15,6 +15,7 @@ var InvalidRivusPlugin = class extends Error {
15
15
  const MEMORY_SCOPES = [
16
16
  "conversation",
17
17
  "agent-private",
18
+ "project",
18
19
  "shared-user-profile"
19
20
  ];
20
21
  const RIVUS_MEMORY_TOOL_ID = "memory";
@@ -36,6 +37,12 @@ function createMemoryNamespace(binding) {
36
37
  binding.subjectId,
37
38
  binding.scope
38
39
  ].map(encode).join("/");
40
+ case "project": return [
41
+ binding.tenantId,
42
+ binding.agentId,
43
+ binding.projectId ?? "",
44
+ binding.scope
45
+ ].map(encode).join("/");
39
46
  case "shared-user-profile": return [
40
47
  binding.tenantId,
41
48
  binding.subjectId,
@@ -44,7 +51,7 @@ function createMemoryNamespace(binding) {
44
51
  }
45
52
  }
46
53
  function restrictMemoryScopesForAudience(scopes, audience) {
47
- return audience === "group" ? scopes.filter((scope) => scope === "conversation") : [...scopes];
54
+ return audience === "group" ? scopes.filter((scope) => scope === "conversation" || scope === "project") : [...scopes];
48
55
  }
49
56
  function createRivusMemoryToolContract(scopes) {
50
57
  return Object.freeze({
@@ -92,7 +99,7 @@ function createRivusMemoryToolContract(scopes) {
92
99
  type: "string"
93
100
  },
94
101
  ...scopes.length === 0 ? {} : { scope: {
95
- description: "Optional Host-granted scope for search or propose. Shared User Profile is read-only to the model.",
102
+ description: "Optional Host-granted scope for search or propose. Confirmed Project and Shared User Profile Memory are read-only to the model.",
96
103
  enum: [...scopes],
97
104
  type: "string"
98
105
  } }
@@ -250,6 +257,7 @@ function resolveRivusAgentDefinition(catalog, deployment) {
250
257
  model: profile.model,
251
258
  pluginId: deployment.pluginId,
252
259
  profileId: deployment.profileId,
260
+ ...deployment.projectSpaceId ? { projectSpaceId: deployment.projectSpaceId } : {},
253
261
  profileRevision,
254
262
  skillGrantSet,
255
263
  skills,
@@ -118,6 +118,7 @@ interface RivusAgentDeployment {
118
118
  readonly agentId: string;
119
119
  readonly pluginId: string;
120
120
  readonly profileId: string;
121
+ readonly projectSpaceId?: string;
121
122
  readonly endpointIds: ReadonlyArray<string>;
122
123
  readonly memory?: {
123
124
  readonly scopes: ReadonlyArray<MemoryScope>;
@@ -152,6 +153,8 @@ interface ResolvedRivusAgentDefinition {
152
153
  readonly agentId: string;
153
154
  readonly pluginId: string;
154
155
  readonly profileId: string;
156
+ readonly projectSpaceId?: string;
157
+ readonly projectSpaceRevision?: string;
155
158
  readonly endpointIds: ReadonlyArray<string>;
156
159
  readonly profileRevision: string;
157
160
  readonly systemPrompt: string;
@@ -1,6 +1,6 @@
1
1
  import { mkdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { createHash } from "node:crypto";
3
- import { join } from "node:path";
3
+ import { join, relative } from "node:path";
4
4
  import { Effect } from "effect";
5
5
  import * as Lark from "@larksuiteoapi/node-sdk";
6
6
  import {
@@ -35,6 +35,7 @@ import {
35
35
  createLangfuseAgentTelemetry,
36
36
  createLazyFeishuWebSocketEventDispatcher,
37
37
  createPiAgentLoop,
38
+ createProjectMemoryPromptPreparer,
38
39
  createPiSkillRuntime,
39
40
  createPiToolNameResolver,
40
41
  createPiToolProxyDefinitions,
@@ -54,6 +55,8 @@ import {
54
55
  openJsonlRecoveryControl,
55
56
  resolveFeishuEndpointCredentials,
56
57
  resolveLangfuseTelemetryConfig,
58
+ validateProjectSkillCatalog,
59
+ validateProjectSkillCommand,
57
60
  type CreateRivusDeploymentEndpointInput,
58
61
  type CreateRivusDeploymentAutomationInput,
59
62
  type CreateRivusDeploymentRuntimeInput,
@@ -63,6 +66,7 @@ import {
63
66
  type RivusDeploymentBootstrapContext,
64
67
  type RivusThinkingLevel
65
68
  } from "@rivus/agent";
69
+ import { createPiProjectSkillReadTool } from "@rivus/agent/pi";
66
70
 
67
71
  type PiSessionOptions = NonNullable<Parameters<typeof createAgentSession>[0]>;
68
72
 
@@ -88,12 +92,6 @@ export async function createRivusDeploymentAdapters(context: RivusDeploymentBoot
88
92
  request: (input) => request(input).pipe(Effect.map((response) => response as ConfiguredFeishuOpenApiResponse))
89
93
  });
90
94
  const interactionRegistry = createHumanInteractionEndpointRegistry();
91
- const workspaceInstructions = await createAgentsMdInstructionsProvider().resolve({
92
- maxBytes: 64 * 1024,
93
- workingDirectory: ".",
94
- workspaceRoot: await createWorkspaceRootHandle(process.cwd())
95
- });
96
-
97
95
  return {
98
96
  dispose: () => telemetry?.shutdown(),
99
97
  createRecoveryControl: () =>
@@ -240,6 +238,7 @@ export async function createRivusDeploymentAdapters(context: RivusDeploymentBoot
240
238
  interactions,
241
239
  maxPendingMessages: 100,
242
240
  memoryTenantId,
241
+ ...(input.projectSpaceId ? { projectSpaceId: input.projectSpaceId } : {}),
243
242
  onCapacityExceeded: (payload) =>
244
243
  replies.reply(payload.event.message.message_id, "Rivus is busy. Please retry in a moment."),
245
244
  prepareRun: createFeishuPresentationPreparation({
@@ -278,10 +277,33 @@ export async function createRivusDeploymentAdapters(context: RivusDeploymentBoot
278
277
  });
279
278
  const resolveToolName = createPiToolNameResolver(input.definition.tools);
280
279
  const skillRuntime = createPiSkillRuntime(input.definition.skills);
280
+ const workingDirectory = input.projectSpace?.workingDirectory ?? process.cwd();
281
+ const workspaceRoot = input.projectSpace?.root ?? process.cwd();
282
+ const workspaceInstructions = await createAgentsMdInstructionsProvider().resolve({
283
+ maxBytes: 64 * 1024,
284
+ workingDirectory: relative(workspaceRoot, workingDirectory) || ".",
285
+ workspaceRoot: await createWorkspaceRootHandle(workspaceRoot)
286
+ });
287
+ const prepareProjectMemory =
288
+ input.projectSpace && input.definition.memory.scopes.includes("project")
289
+ ? createProjectMemoryPromptPreparer({
290
+ agentId: input.agentId,
291
+ memory,
292
+ projectId: input.projectSpace.id
293
+ })
294
+ : undefined;
281
295
  const sessionRegistry = createPiSessionRegistry({
282
296
  createSession: async (firstInput) => {
283
297
  let activeInput = firstInput;
284
298
  const customTools = [
299
+ ...(input.projectSpace
300
+ ? [
301
+ createPiProjectSkillReadTool({
302
+ cwd: input.projectSpace.workingDirectory,
303
+ skillPaths: input.projectSpace.skillPaths
304
+ })
305
+ ]
306
+ : []),
285
307
  ...createPiToolProxyDefinitions({
286
308
  agentId: input.agentId,
287
309
  approvals: createHumanInteractionToolApprovalGateway({ registry: interactionRegistry }),
@@ -296,9 +318,10 @@ export async function createRivusDeploymentAdapters(context: RivusDeploymentBoot
296
318
  ];
297
319
  const resourceLoader = new DefaultResourceLoader({
298
320
  agentDir: PI_AGENT_DIR,
321
+ ...(input.projectSpace ? { additionalSkillPaths: [...input.projectSpace.skillPaths] } : {}),
299
322
  appendSystemPromptOverride: () =>
300
323
  [workspaceInstructions.content, skillRuntime.prompt].filter((content) => content.length > 0),
301
- cwd: process.cwd(),
324
+ cwd: workingDirectory,
302
325
  noContextFiles: true,
303
326
  noExtensions: true,
304
327
  noPromptTemplates: true,
@@ -307,11 +330,14 @@ export async function createRivusDeploymentAdapters(context: RivusDeploymentBoot
307
330
  systemPromptOverride: () => input.definition.systemPrompt
308
331
  });
309
332
  await resourceLoader.reload();
333
+ const nativeSkillNames = input.projectSpace
334
+ ? validateProjectSkillCatalog(resourceLoader.getSkills())
335
+ : new Set<string>();
310
336
  const result = await createAgentSession({
311
337
  ...piOptions,
312
338
  customTools,
313
339
  resourceLoader,
314
- sessionManager: SessionManager.create(process.cwd(), join(instanceState, "sessions")),
340
+ sessionManager: SessionManager.create(workingDirectory, join(instanceState, "sessions")),
315
341
  tools: customTools.map(({ name }) => name)
316
342
  });
317
343
  return {
@@ -319,6 +345,10 @@ export async function createRivusDeploymentAdapters(context: RivusDeploymentBoot
319
345
  activeInput = loopInput;
320
346
  },
321
347
  dispose: () => result.session.dispose(),
348
+ preparePrompt: async (loopInput) => {
349
+ validateProjectSkillCommand(loopInput.text, nativeSkillNames);
350
+ return prepareProjectMemory ? prepareProjectMemory(loopInput) : loopInput.text;
351
+ },
322
352
  resolveToolName,
323
353
  session: result.session
324
354
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rivus/agent",
3
- "version": "0.4.1",
3
+ "version": "0.5.1",
4
4
  "description": "A local agent daemon core built around a usable agent harness and domain events.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -34,6 +34,10 @@
34
34
  "types": "./dist/acp.d.ts",
35
35
  "import": "./dist/acp.js"
36
36
  },
37
+ "./pi": {
38
+ "types": "./dist/pi.d.ts",
39
+ "import": "./dist/pi.js"
40
+ },
37
41
  "./testing": {
38
42
  "types": "./dist/testing/index.d.ts",
39
43
  "import": "./dist/testing/index.js"