@rivus/agent 0.6.2 → 0.7.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.
@@ -1,5 +1,5 @@
1
1
  import { mkdir, readFile, writeFile } from "node:fs/promises";
2
- import { createHash } from "node:crypto";
2
+ import { createHash, randomUUID } from "node:crypto";
3
3
  import { join, relative } from "node:path";
4
4
  import { Effect } from "effect";
5
5
  import * as Lark from "@larksuiteoapi/node-sdk";
@@ -14,8 +14,13 @@ import {
14
14
  createAgentsMdInstructionsProvider,
15
15
  createAgentHarness,
16
16
  createAgentHarnessPooledRuntime,
17
+ createBackgroundSessionHostTools,
18
+ createBackgroundSessionService,
19
+ createBackgroundSessionStepSourceMessageId,
20
+ createBackgroundSessionSupervisor,
17
21
  createRivusMemoryToolDescriptor,
18
22
  createConfiguredFeishuAutomationCardSender,
23
+ createConfiguredFeishuBackgroundSessionDelivery,
19
24
  createConfiguredFeishuCardRolloverRuntime,
20
25
  createConfiguredFeishuHumanInteractionPresenter,
21
26
  createConfiguredFeishuOpenApiClient,
@@ -43,6 +48,10 @@ import {
43
48
  createToolBroker,
44
49
  createUuidRunIds,
45
50
  createWorkspaceRootHandle,
51
+ loadRivusDeploymentManifest,
52
+ openJsonlBackgroundSessionDeliveryStore,
53
+ openJsonlBackgroundSessionRepository,
54
+ resolveBackgroundSessionSupervisorIntervalMs,
46
55
  openJsonlFeishuCardDeliveryLedger,
47
56
  openJsonlFeishuInboxRepository,
48
57
  openJsonlAgentMemoryService,
@@ -53,13 +62,16 @@ import {
53
62
  resolveLangfuseTelemetryConfig,
54
63
  validateProjectSkillCatalog,
55
64
  validateProjectSkillCommand,
65
+ type CreateRivusDeploymentBackgroundSessionInput,
56
66
  type CreateRivusDeploymentEndpointInput,
57
67
  type CreateRivusDeploymentAutomationInput,
58
68
  type CreateRivusDeploymentRuntimeInput,
59
69
  type ConfiguredFeishuOpenApiResponse,
60
70
  type FeishuAgentRunPreparation,
71
+ type FeishuBackgroundSessionDelivery,
61
72
  type FeishuWebSocketClient,
62
73
  type RivusDaemonConfig,
74
+ type RivusDeploymentBackgroundSession,
63
75
  type RivusDeploymentBootstrapContext,
64
76
  type RivusThinkingLevel
65
77
  } from "@rivus/agent";
@@ -94,6 +106,144 @@ export async function createRivusDeploymentAdapters(context: RivusDeploymentBoot
94
106
  request: (input) => request(input).pipe(Effect.map((response) => response as ConfiguredFeishuOpenApiResponse))
95
107
  });
96
108
  const interactionRegistry = createHumanInteractionEndpointRegistry();
109
+ const manifest = await loadRivusDeploymentManifest(context.manifestPath);
110
+ const backgroundSessionsConfig = manifest.backgroundSessions;
111
+ const sessionRepository = backgroundSessionsConfig?.enabled
112
+ ? await openJsonlBackgroundSessionRepository({
113
+ filePath: join(STATE_DIR, "background-sessions", "sessions.jsonl")
114
+ })
115
+ : undefined;
116
+ const sessionDeliveries = backgroundSessionsConfig?.enabled
117
+ ? await openJsonlBackgroundSessionDeliveryStore({
118
+ filePath: join(STATE_DIR, "background-sessions", "deliveries.jsonl")
119
+ })
120
+ : undefined;
121
+ const deliveryClients = new Map<string, FeishuBackgroundSessionDelivery>();
122
+ const backgroundService = backgroundSessionsConfig?.enabled
123
+ ? createBackgroundSessionService({
124
+ clock: { now: () => new Date().toISOString() },
125
+ deliveries: sessionDeliveries!,
126
+ repository: sessionRepository!
127
+ })
128
+ : undefined;
129
+ const createBackgroundSessionAdapter = (
130
+ input: CreateRivusDeploymentBackgroundSessionInput
131
+ ): RivusDeploymentBackgroundSession => {
132
+ const resolveDeliverySender = async (endpointId: string): Promise<FeishuBackgroundSessionDelivery> => {
133
+ const existing = deliveryClients.get(endpointId);
134
+ if (existing) return existing;
135
+ const endpoint = manifest.endpoints.find(({ id }) => id === endpointId);
136
+ if (!endpoint) throw new Error(`background session delivery endpoint not found: ${endpointId}`);
137
+ const credentials = resolveFeishuEndpointCredentials(endpoint.credentialRef, context.env);
138
+ const config: RivusDaemonConfig = {
139
+ agentId: endpoint.agentId,
140
+ feishu: {
141
+ ...credentials,
142
+ baseUrl: endpoint.baseUrl,
143
+ cardStreamLeaseMs: endpoint.cardStreamLeaseMs,
144
+ streamMinIntervalMs: endpoint.streamMinIntervalMs
145
+ },
146
+ pi: {}
147
+ };
148
+ const sender = createConfiguredFeishuBackgroundSessionDelivery({
149
+ client: createOpenApiClient(config),
150
+ config
151
+ });
152
+ deliveryClients.set(endpointId, sender);
153
+ return sender;
154
+ };
155
+ const supervisor = createBackgroundSessionSupervisor({
156
+ clock: { now: () => new Date().toISOString() },
157
+ config: {
158
+ intervalMs: resolveBackgroundSessionSupervisorIntervalMs(input.config.leaseMs),
159
+ leaseMs: input.config.leaseMs,
160
+ leaseRenewalIntervalMs: input.config.leaseRenewalIntervalMs,
161
+ maxConcurrentSessions: input.config.maxConcurrentSessions,
162
+ maxConsecutiveFailures: input.config.maxConsecutiveFailures,
163
+ retryBackoffMs: input.config.retryBackoffMs,
164
+ sessionLifetimeMs: input.config.sessionLifetimeMs
165
+ },
166
+ deliveries: sessionDeliveries!,
167
+ deliver: async (delivery) => {
168
+ const session = await sessionRepository!.get(delivery.sessionId);
169
+ if (!session) throw new Error(`background session not found for delivery: ${delivery.sessionId}`);
170
+ if (!session.origin.conversationId) {
171
+ throw new Error(`background session has no delivery conversation: ${delivery.sessionId}`);
172
+ }
173
+ const sender = await resolveDeliverySender(session.origin.endpointId);
174
+ return sender.deliver({
175
+ chatId: session.origin.conversationId,
176
+ deliveryId: delivery.deliveryId,
177
+ displayName: session.displayName,
178
+ kind: delivery.kind,
179
+ sessionId: session.sessionId,
180
+ text: delivery.text
181
+ });
182
+ },
183
+ onError: (error) => {
184
+ console.error("Background session supervisor failed", error);
185
+ },
186
+ repository: sessionRepository!,
187
+ runStep: async ({ session, signal, wakeText }) => {
188
+ let runId: string | undefined;
189
+ const invocation = {
190
+ allowedActorOpenIds: session.origin.allowedActorOpenIds,
191
+ endpointId: session.origin.endpointId,
192
+ kind: "background-session" as const,
193
+ ...(session.origin.memory ? { memory: session.origin.memory } : {}),
194
+ sessionId: session.sessionId,
195
+ sourceMessageId: createBackgroundSessionStepSourceMessageId(session.sessionId, session.stepCount + 1),
196
+ tenantKey: session.origin.tenantKey
197
+ };
198
+ const abortPromise = new Promise<never>((_resolve, reject) => {
199
+ signal.addEventListener(
200
+ "abort",
201
+ () => {
202
+ if (runId) {
203
+ void input.cancel({
204
+ agentId: session.authority.agentId,
205
+ reason: "background session step aborted",
206
+ runId,
207
+ sessionKey: session.authority.sessionKey
208
+ });
209
+ }
210
+ reject(new Error("background session step aborted"));
211
+ },
212
+ { once: true }
213
+ );
214
+ });
215
+ const runPromise = input
216
+ .run({
217
+ agentId: session.authority.agentId,
218
+ invocation,
219
+ onUpdate: (update) => {
220
+ if (update.event.type === "agent_run_accepted" && !runId) {
221
+ runId = update.event.runId;
222
+ }
223
+ },
224
+ sessionKey: session.authority.sessionKey,
225
+ text: wakeText
226
+ })
227
+ .then((result) => readStepRunResult(result));
228
+ return Promise.race([runPromise, abortPromise]);
229
+ },
230
+ sleep
231
+ });
232
+ let running = false;
233
+ return {
234
+ running: () => running,
235
+ status: () => supervisor.status(),
236
+ start: async () => {
237
+ await Effect.runPromise(supervisor.recover());
238
+ await Effect.runPromise(supervisor.start());
239
+ running = true;
240
+ },
241
+ stop: async () => {
242
+ await Effect.runPromise(supervisor.stop());
243
+ running = false;
244
+ }
245
+ };
246
+ };
97
247
  return {
98
248
  dispose: () => telemetry?.shutdown(),
99
249
  createRecoveryControl: () =>
@@ -101,6 +251,9 @@ export async function createRivusDeploymentAdapters(context: RivusDeploymentBoot
101
251
  endpointsDirectory: join(STATE_DIR, "endpoints"),
102
252
  instancesDirectory: join(STATE_DIR, "instances")
103
253
  }),
254
+ createBackgroundSession: backgroundSessionsConfig?.enabled
255
+ ? (input: CreateRivusDeploymentBackgroundSessionInput) => createBackgroundSessionAdapter(input)
256
+ : undefined,
104
257
  createAutomation: async (input: CreateRivusDeploymentAutomationInput) => {
105
258
  const credentials = resolveFeishuEndpointCredentials(input.deliveryEndpoint.credentialRef, context.env);
106
259
  const config: RivusDaemonConfig = {
@@ -278,7 +431,18 @@ export async function createRivusDeploymentAdapters(context: RivusDeploymentBoot
278
431
  const broker = createToolBroker({
279
432
  approvals: createRoutedHumanInteractionToolApprovalService(interactionRegistry),
280
433
  catalog: input.catalog,
281
- ...(input.definition.memory.tool ? { hostTools: [createRivusMemoryToolDescriptor({ memory })] } : {}),
434
+ hostTools: [
435
+ ...(input.definition.memory.tool ? [createRivusMemoryToolDescriptor({ memory })] : []),
436
+ ...(backgroundService
437
+ ? [
438
+ ...createBackgroundSessionHostTools({
439
+ createSessionId: () => `bg-${randomUUID()}`,
440
+ definition: input.definition,
441
+ service: backgroundService
442
+ })
443
+ ]
444
+ : [])
445
+ ],
282
446
  operations: await openJsonlToolOperationLedger({ filePath: join(instanceState, "tool-operations.jsonl") }),
283
447
  policy: { current: async () => ({ epoch: 1, revokedToolIds: [] }) }
284
448
  });
@@ -374,10 +538,14 @@ export async function createRivusDeploymentAdapters(context: RivusDeploymentBoot
374
538
  eventSinks: telemetry ? [eventLog, telemetry.sink] : [eventLog],
375
539
  initialEvents: eventsForSession(initialEvents, sessionKey),
376
540
  loop,
377
- runIds: createUuidRunIds()
541
+ runIds: createUuidRunIds(),
542
+ ...(input.binding.kind === "background-session" && backgroundSessionsConfig
543
+ ? { runTimeoutMs: backgroundSessionsConfig.stepTimeoutMs }
544
+ : {})
378
545
  })
379
546
  ),
380
- maxConcurrentSessions: 4,
547
+ maxConcurrentSessions:
548
+ input.binding.kind === "background-session" ? (backgroundSessionsConfig?.maxConcurrentSessions ?? 4) : 4,
381
549
  maxQueuedRuns: 32
382
550
  });
383
551
  return {
@@ -439,6 +607,21 @@ function readAutomationRunResult(result: unknown): { readonly body: string; read
439
607
  throw new Error("Scheduled Automation Agent Run did not produce a runId and final text");
440
608
  }
441
609
 
610
+ function readStepRunResult(result: unknown): { readonly finalText: string; readonly runId: string } {
611
+ if (
612
+ result !== null &&
613
+ typeof result === "object" &&
614
+ "finalText" in result &&
615
+ typeof result.finalText === "string" &&
616
+ "runId" in result &&
617
+ typeof result.runId === "string" &&
618
+ result.runId.trim() !== ""
619
+ ) {
620
+ return { finalText: result.finalText, runId: result.runId };
621
+ }
622
+ throw new Error("Background session Agent Run did not produce a runId and final text");
623
+ }
624
+
442
625
  function createLazyFeishuWebSocketClient(
443
626
  credentials: {
444
627
  readonly appId: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rivus/agent",
3
- "version": "0.6.2",
3
+ "version": "0.7.0",
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",