@rivus/agent 0.16.2 → 0.16.6

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,894 +1,3 @@
1
- import { createPiManagedSessionContext } from "../src/adapters/pi/model-management/pi-managed-session-context.js";
2
- import { ensurePiModel } from "../src/adapters/pi/model-management/pi-model-resolution.js";
3
- import { openPiModelManagementDeployment } from "../src/bootstrap/deployment/model-management/pi-model-management-deployment.js";
4
- import { mkdir, readFile, stat } from "node:fs/promises";
5
- import { createHash, randomUUID } from "node:crypto";
6
- import { homedir } from "node:os";
7
- import { join, relative } from "node:path";
8
- import { Effect } from "effect";
9
- import * as Lark from "@larksuiteoapi/node-sdk";
10
- import { createAgentSession, ModelRuntime, SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
11
- import {
12
- createAgentsMdInstructionsProvider,
13
- createAgentHarness,
14
- createAgentHarnessPooledRuntime,
15
- createBackgroundSessionHostTools,
16
- createBackgroundSessionService,
17
- createBackgroundSessionStepSourceMessageId,
18
- createBackgroundSessionSupervisor,
19
- createRivusMemoryToolDescriptor,
20
- createConfiguredFeishuAutomationCardSender,
21
- createConfiguredFeishuBackgroundSessionDelivery,
22
- createConfiguredFeishuCardRolloverRuntime,
23
- createConfiguredFeishuHumanInteractionPresenter,
24
- createConfiguredFeishuMessageReactionSender,
25
- createConfiguredFeishuOpenApiClient,
26
- createConfiguredFeishuTextReplySender,
27
- createFeishuCotPublisher,
28
- createFeishuDeploymentEndpoint,
29
- createFeishuPresentationPreparation,
30
- createFeishuTopicContextResolver,
31
- createSystemClock,
32
- createFeishuCardDeliveryReconciler,
33
- createHumanInteractionEndpointRegistry,
34
- createHumanInteractionService,
35
- createHumanInteractionToolApprovalGateway,
36
- createHumanInteractionModelChangeApproval,
37
- createJsonFetchRequest,
38
- createJsonFileFeishuCardTargetRegistry,
39
- createJsonlAgentEventLog,
40
- createJsonlHumanInteractionRepository,
41
- createLangfuseAgentTelemetry,
42
- createLazyFeishuWebSocketEventDispatcher,
43
- createPiAgentLoop,
44
- createProjectMemoryPromptPreparer,
45
- createPiSessionRegistry,
46
- createRoutedHumanInteractionToolApprovalService,
47
- createSessionScheduler,
48
- AUTOMATION_SUPPRESSION_PREFIX,
49
- createScheduledAutomation,
50
- createToolBroker,
51
- createUuidRunIds,
52
- createWorkspaceRootHandle,
53
- loadRivusDeploymentManifest,
54
- mergePiProviderBaseUrlOverride,
55
- openJsonlBackgroundSessionDeliveryStore,
56
- openJsonlBackgroundSessionRepository,
57
- resolveBackgroundSessionSupervisorIntervalMs,
58
- resolveFeishuDeliveryChatId,
59
- openJsonlFeishuCardDeliveryLedger,
60
- openJsonlFeishuInboxRepository,
61
- openJsonFeishuSessionStore,
62
- openJsonlAgentMemoryService,
63
- openJsonAutomationTickRepository,
64
- openJsonlToolOperationLedger,
65
- openJsonlRecoveryControl,
66
- readAutomationPresentation,
67
- resolveFeishuEndpointCredentials,
68
- resolveLangfuseTelemetryConfig,
69
- type CreateRivusDeploymentBackgroundSessionInput,
70
- type CreateRivusDeploymentEndpointInput,
71
- type CreateRivusDeploymentAutomationInput,
72
- type CreateRivusDeploymentRuntimeInput,
73
- type ConfiguredFeishuOpenApiResponse,
74
- type FeishuAgentRunPreparation,
75
- type FeishuBackgroundSessionDelivery,
76
- type FeishuWebSocketClient,
77
- type RivusDaemonConfig,
78
- type RivusDaemonEnv,
79
- type RivusEndpointDeployment,
80
- type RivusDeploymentBackgroundSession,
81
- type RivusDeploymentBootstrapContext,
82
- type RivusThinkingLevel
83
- } from "@rivus/agent";
84
- import {
85
- createPiBashTool,
86
- createPiSkillReadTools,
87
- createPiSessionResources,
88
- createPiSkillRuntime,
89
- resolvePiSessionToolNames,
90
- createPiToolNameResolver,
91
- createPiToolProxyDefinitions,
92
- validatePiSkillCommand
93
- } from "@rivus/agent/pi";
94
-
95
- type PiSessionOptions = NonNullable<Parameters<typeof createAgentSession>[0]>;
96
-
97
- const STATE_DIR = process.env.RIVUS_DEPLOYMENT_STATE_DIR?.trim() || ".rivus/deployment";
98
- const PI_AGENT_DIR = join(STATE_DIR, "pi-agent");
99
- const PI_AUTH_FILE = join(STATE_DIR, "pi-auth.json");
100
- const PI_MODELS_FILE = join(STATE_DIR, "pi-models.json");
101
- const THINKING_LEVELS = new Set<RivusThinkingLevel>(["off", "minimal", "low", "medium", "high", "xhigh"]);
102
-
103
- export async function createRivusDeploymentAdapters(context: RivusDeploymentBootstrapContext) {
104
- await mkdir(PI_AGENT_DIR, { recursive: true });
105
- const hasManagedState = await stat(join(STATE_DIR, "model-management", "model-state.json")).then(
106
- () => true,
107
- (error: NodeJS.ErrnoException) => {
108
- if (error.code === "ENOENT") return false;
109
- throw error;
110
- }
111
- );
112
- const deferModel = context.env.RIVUS_MODEL_MANAGEMENT_ENABLED?.trim() === "true" || hasManagedState;
113
- const piOptions = await createPiSessionOptions(context, deferModel);
114
- const telemetryConfig = resolveLangfuseTelemetryConfig(context.env);
115
- let telemetry: ReturnType<typeof createLangfuseAgentTelemetry> | undefined;
116
- const memory = await openJsonlAgentMemoryService({
117
- filePath: join(STATE_DIR, "memory", "agent-memory.jsonl")
118
- });
119
- const memoryTenantId = context.env.RIVUS_MEMORY_TENANT_ID?.trim() || "local";
120
- const request = createJsonFetchRequest();
121
- const createOpenApiClient = (config: RivusDaemonConfig) =>
122
- createConfiguredFeishuOpenApiClient({
123
- config,
124
- request: (input) => request(input).pipe(Effect.map((response) => response as ConfiguredFeishuOpenApiResponse))
125
- });
126
- const interactionRegistry = createHumanInteractionEndpointRegistry();
127
- const manifest = await loadRivusDeploymentManifest(context.manifestPath);
128
- const defaultEndpoint = manifest.endpoints.find((endpoint) => endpoint.id === manifest.defaultEndpointId)!;
129
- const management = await openPiModelManagementDeployment({
130
- agentId: manifest.defaultAgentId,
131
- approval: createHumanInteractionModelChangeApproval({
132
- endpointId: manifest.defaultEndpointId,
133
- registry: interactionRegistry
134
- }),
135
- agentCount: manifest.agents.length,
136
- endpointId: manifest.defaultEndpointId,
137
- env: context.env,
138
- environmentOverrides: context.environmentOverrides ?? process.env,
139
- ...(context.envFilePath ? { envFilePath: context.envFilePath } : {}),
140
- modelAuthPath: PI_AUTH_FILE,
141
- modelCatalogPath: PI_MODELS_FILE,
142
- modelRuntime: piOptions.modelRuntime!,
143
- reply: (messageId, text) => {
144
- const config = createEndpointConfig(defaultEndpoint, manifest.defaultAgentId, context.env);
145
- return createConfiguredFeishuTextReplySender({ config, client: createOpenApiClient(config) }).reply(
146
- messageId,
147
- text
148
- );
149
- },
150
- stateDirectory: STATE_DIR,
151
- thinkingLevel: piOptions.thinkingLevel ?? "medium"
152
- });
153
- try {
154
- telemetry = telemetryConfig ? createLangfuseAgentTelemetry(telemetryConfig) : undefined;
155
- if (management && !management.enabled) {
156
- piOptions.model = await ensurePiModel({
157
- modelRuntime: piOptions.modelRuntime!,
158
- modelsPath: PI_MODELS_FILE,
159
- target: management.model
160
- });
161
- }
162
- if (management?.enabled && management.recoveryRequired) {
163
- console.error("Model management requires recovery; business Runs are paused. Query rivus model status --json.");
164
- }
165
- if (management?.enabled && management.skillInstallation.status === "conflict") {
166
- console.error("The runtime-management Skill has local edits; its installed instructions were preserved.");
167
- }
168
- const backgroundSessionsConfig = manifest.backgroundSessions;
169
- const sessionRepository = backgroundSessionsConfig?.enabled
170
- ? await openJsonlBackgroundSessionRepository({
171
- filePath: join(STATE_DIR, "background-sessions", "sessions.jsonl")
172
- })
173
- : undefined;
174
- const sessionDeliveries = backgroundSessionsConfig?.enabled
175
- ? await openJsonlBackgroundSessionDeliveryStore({
176
- filePath: join(STATE_DIR, "background-sessions", "deliveries.jsonl")
177
- })
178
- : undefined;
179
- const deliveryClients = new Map<string, FeishuBackgroundSessionDelivery>();
180
- const backgroundService = backgroundSessionsConfig?.enabled
181
- ? createBackgroundSessionService({
182
- clock: { now: () => new Date().toISOString() },
183
- deliveries: sessionDeliveries!,
184
- repository: sessionRepository!
185
- })
186
- : undefined;
187
- const createBackgroundSessionAdapter = (
188
- input: CreateRivusDeploymentBackgroundSessionInput
189
- ): RivusDeploymentBackgroundSession => {
190
- const resolveDeliverySender = async (endpointId: string): Promise<FeishuBackgroundSessionDelivery> => {
191
- const existing = deliveryClients.get(endpointId);
192
- if (existing) return existing;
193
- const endpoint = manifest.endpoints.find(({ id }) => id === endpointId);
194
- if (!endpoint) throw new Error(`background session delivery endpoint not found: ${endpointId}`);
195
- const config = createEndpointConfig(endpoint, endpoint.agentId, context.env);
196
- const sender = createConfiguredFeishuBackgroundSessionDelivery({
197
- client: createOpenApiClient(config),
198
- config
199
- });
200
- deliveryClients.set(endpointId, sender);
201
- return sender;
202
- };
203
- const supervisor = createBackgroundSessionSupervisor({
204
- clock: { now: () => new Date().toISOString() },
205
- config: {
206
- intervalMs: resolveBackgroundSessionSupervisorIntervalMs(input.config.leaseMs),
207
- leaseMs: input.config.leaseMs,
208
- leaseRenewalIntervalMs: input.config.leaseRenewalIntervalMs,
209
- maxConcurrentSessions: input.config.maxConcurrentSessions,
210
- maxConsecutiveFailures: input.config.maxConsecutiveFailures,
211
- retryBackoffMs: input.config.retryBackoffMs,
212
- sessionLifetimeMs: input.config.sessionLifetimeMs
213
- },
214
- deliveries: sessionDeliveries!,
215
- deliver: async (delivery) => {
216
- const session = await sessionRepository!.get(delivery.sessionId);
217
- if (!session) throw new Error(`background session not found for delivery: ${delivery.sessionId}`);
218
- if (!session.origin.conversationId) {
219
- throw new Error(`background session has no delivery conversation: ${delivery.sessionId}`);
220
- }
221
- const sender = await resolveDeliverySender(session.origin.endpointId);
222
- return sender.deliver({
223
- chatId: resolveFeishuDeliveryChatId(session.origin.conversationId),
224
- deliveryId: delivery.deliveryId,
225
- displayName: session.displayName,
226
- kind: delivery.kind,
227
- sessionId: session.sessionId,
228
- text: delivery.text
229
- });
230
- },
231
- onError: (error) => {
232
- console.error("Background session supervisor failed", error);
233
- },
234
- repository: sessionRepository!,
235
- runStep: async ({ session, signal, wakeText }) => {
236
- let runId: string | undefined;
237
- const invocation = {
238
- allowedActorOpenIds: session.origin.allowedActorOpenIds,
239
- ...(session.origin.conversationId ? { conversationId: session.origin.conversationId } : {}),
240
- endpointId: session.origin.endpointId,
241
- kind: "background-session" as const,
242
- ...(session.origin.memory ? { memory: session.origin.memory } : {}),
243
- sessionId: session.sessionId,
244
- sourceMessageId: createBackgroundSessionStepSourceMessageId(session.sessionId, session.stepCount + 1),
245
- tenantKey: session.origin.tenantKey
246
- };
247
- const abortPromise = new Promise<never>((_resolve, reject) => {
248
- signal.addEventListener(
249
- "abort",
250
- () => {
251
- if (runId) {
252
- void input.cancel({
253
- agentId: session.authority.agentId,
254
- reason: "background session step aborted",
255
- runId,
256
- sessionKey: session.authority.sessionKey
257
- });
258
- }
259
- reject(new Error("background session step aborted"));
260
- },
261
- { once: true }
262
- );
263
- });
264
- const runPromise = input
265
- .run({
266
- agentId: session.authority.agentId,
267
- invocation,
268
- onUpdate: (update) => {
269
- if (update.event.type === "agent_run_accepted" && !runId) {
270
- runId = update.event.runId;
271
- }
272
- },
273
- sessionKey: session.authority.sessionKey,
274
- text: wakeText
275
- })
276
- .then((result) => readStepRunResult(result));
277
- return Promise.race([runPromise, abortPromise]);
278
- },
279
- sleep
280
- });
281
- let running = false;
282
- return {
283
- running: () => running,
284
- status: () => supervisor.status(),
285
- start: async () => {
286
- await Effect.runPromise(supervisor.recover());
287
- await Effect.runPromise(supervisor.start());
288
- running = true;
289
- },
290
- stop: async () => {
291
- await Effect.runPromise(supervisor.stop());
292
- running = false;
293
- }
294
- };
295
- };
296
- return {
297
- dispose: async () => {
298
- if (management?.enabled) await management.close();
299
- await telemetry?.shutdown();
300
- },
301
- createRecoveryControl: () =>
302
- openJsonlRecoveryControl({
303
- endpointsDirectory: join(STATE_DIR, "endpoints"),
304
- instancesDirectory: join(STATE_DIR, "instances")
305
- }),
306
- createBackgroundSession: backgroundSessionsConfig?.enabled
307
- ? (input: CreateRivusDeploymentBackgroundSessionInput) => createBackgroundSessionAdapter(input)
308
- : undefined,
309
- createAutomation: async (input: CreateRivusDeploymentAutomationInput) => {
310
- const config = createEndpointConfig(input.deliveryEndpoint, input.definition.agentId, context.env);
311
- const openApiClient = createOpenApiClient(config);
312
- const sender = createConfiguredFeishuAutomationCardSender({
313
- client: openApiClient,
314
- config
315
- });
316
- const repository = await openJsonAutomationTickRepository({
317
- filePath: join(
318
- STATE_DIR,
319
- "automations",
320
- createHash("sha256").update(input.automationId).digest("hex").slice(0, 16),
321
- "ticks.json"
322
- )
323
- });
324
- const target = resolveAutomationTarget(input.definition.delivery.targetRef, context.env);
325
- const automation = createScheduledAutomation({
326
- automationId: input.automationId,
327
- binding: {
328
- agentId: input.definition.agentId,
329
- bindingId: input.automationId,
330
- delivery: {
331
- endpointId: input.definition.delivery.endpointId,
332
- targetKey: createHash("sha256").update(target).digest("hex"),
333
- targetType: input.definition.delivery.targetType
334
- },
335
- schedule: input.definition.schedule,
336
- servicePrincipalId: `automation:${input.automationId}`,
337
- skillGrantRevision: input.definition.runtimeDefinition.skillGrantSet.revision,
338
- skillIds: input.definition.template.requestedSkillIds,
339
- templateId: input.definition.templateId,
340
- timeZone: input.definition.timeZone,
341
- toolIds: input.definition.template.requestedToolIds
342
- },
343
- createInput: input.definition.template.createInput,
344
- deliver: ({ body, idempotencyKey, presentation }) =>
345
- Effect.runPromise(
346
- sender.send({
347
- idempotencyKey,
348
- receiveId: target,
349
- receiveIdType: input.definition.delivery.targetType,
350
- markdown: body,
351
- ...(presentation === undefined ? {} : { presentation })
352
- })
353
- ),
354
- onError: () => {
355
- console.error(`Scheduled Automation ${input.automationId} failed; it will retry`);
356
- },
357
- repository,
358
- run: async (runInput) => {
359
- const result = readAutomationRunResult(await input.run(runInput));
360
- const projected =
361
- result.body === undefined
362
- ? undefined
363
- : input.definition.template.createPresentation?.({
364
- occurrence: runInput.occurrence,
365
- text: result.body
366
- });
367
- const presentation = projected ? readAutomationPresentation(projected) : undefined;
368
- return { ...result, ...(presentation === undefined ? {} : { presentation }) };
369
- }
370
- });
371
- return automation;
372
- },
373
- createEndpoint: async (input: CreateRivusDeploymentEndpointInput) => {
374
- const credentials = resolveFeishuEndpointCredentials(input.definition.credentialRef, context.env);
375
- const botOpenId = await resolveFeishuBotOpenId(credentials, input.definition.baseUrl);
376
- const endpointState = join(STATE_DIR, "endpoints", input.endpointId);
377
- const cardTargets = createJsonFileFeishuCardTargetRegistry({
378
- filePath: join(endpointState, "feishu-card-targets.json")
379
- });
380
- const cardLedger = await openJsonlFeishuCardDeliveryLedger({
381
- filePath: join(endpointState, "feishu-card-delivery.jsonl")
382
- });
383
- const inbox = await openJsonlFeishuInboxRepository({ filePath: join(endpointState, "feishu-inbox.jsonl") });
384
- const sessionStore = await openJsonFeishuSessionStore({
385
- filePath: join(endpointState, "feishu-session-store.json")
386
- });
387
- const config = createEndpointConfig(input.definition, input.agentId, context.env);
388
- const openApiClient = createOpenApiClient(config);
389
- const promptContext = createFeishuTopicContextResolver({
390
- baseUrl: input.definition.baseUrl,
391
- client: openApiClient
392
- });
393
- const cotPublisher = input.definition.experimental?.cotMessages
394
- ? createFeishuCotPublisher({
395
- baseUrl: resolveExperimentalCotBaseUrl(context.env),
396
- client: openApiClient,
397
- minIntervalMs: input.definition.streamMinIntervalMs
398
- })
399
- : undefined;
400
- const interactions = createHumanInteractionService({
401
- clock: { now: () => new Date().toISOString() },
402
- presenter: createConfiguredFeishuHumanInteractionPresenter({
403
- client: openApiClient,
404
- config
405
- }),
406
- repository: createJsonlHumanInteractionRepository({
407
- filePath: join(endpointState, "human-interactions.jsonl")
408
- })
409
- });
410
- const cardRollover = createConfiguredFeishuCardRolloverRuntime({
411
- agentName: input.agentId,
412
- cardTargets,
413
- client: openApiClient,
414
- clock: createSystemClock(),
415
- config,
416
- ledger: cardLedger,
417
- onError: (error) => {
418
- console.error(`Feishu card rollover failed for endpoint ${input.endpointId}`, error);
419
- },
420
- ...(input.definition.progressDisplay === undefined
421
- ? {}
422
- : { progressDisplay: input.definition.progressDisplay }),
423
- sleep,
424
- title: input.agentId
425
- });
426
- const publisher = cardRollover.rollover;
427
- const endpointEvents = createJsonlAgentEventLog({
428
- filePath: join(STATE_DIR, "instances", input.instanceId, "agent-events.jsonl")
429
- });
430
- const endpointInitialEvents = await Effect.runPromise(endpointEvents.readAll());
431
- await Effect.runPromise(
432
- createFeishuCardDeliveryReconciler({
433
- events: endpointInitialEvents,
434
- ledger: cardLedger,
435
- publish: (action) => publisher.publish(action)
436
- }).reconcile()
437
- );
438
- const replies = createConfiguredFeishuTextReplySender({
439
- client: openApiClient,
440
- config
441
- });
442
- const reactions = createConfiguredFeishuMessageReactionSender({
443
- client: openApiClient,
444
- config
445
- });
446
- const prepareCardTarget = (run: FeishuAgentRunPreparation) => cardRollover.prepareRun(run);
447
- const endpoint = createFeishuDeploymentEndpoint({
448
- agentId: input.agentId,
449
- acknowledgeSteering: (messageId) => reactions.add(messageId),
450
- botOpenId,
451
- cardRollover: cardRollover.transport,
452
- finalizeRun: (runId) => cardRollover.rollover.releaseRun(runId),
453
- cancel: input.cancel,
454
- endpointId: input.endpointId,
455
- eventDispatcher: createLazyFeishuWebSocketEventDispatcher(() => new Lark.EventDispatcher({})),
456
- groupPolicy: input.definition.groupPolicy,
457
- handle: input.handle,
458
- inboxRepository: inbox,
459
- initialEvents: endpointInitialEvents,
460
- interactions,
461
- maxPendingMessages: 100,
462
- memoryTenantId,
463
- ...(input.projectSpaceId ? { projectSpaceId: input.projectSpaceId } : {}),
464
- onCapacityExceeded: (payload) =>
465
- replies.reply(payload.event.message.message_id, "Rivus is busy. Please retry in a moment."),
466
- prepareRun: createFeishuPresentationPreparation({
467
- ...(cotPublisher ? { cotPublisher } : {}),
468
- prepareCardTarget,
469
- reportCotError: (operation, error) => reportCotError(input.endpointId, operation, error)
470
- }),
471
- promptContext,
472
- publish: (action) => publisher.publish(action),
473
- reply: (messageId, text) => replies.reply(messageId, text),
474
- sessionStore,
475
- ...(input.steer ? { steer: input.steer } : {}),
476
- ...(cotPublisher
477
- ? {
478
- publishRunUpdate: (update) =>
479
- cotPublisher
480
- .publish(update)
481
- .pipe(Effect.catchAll((error) => reportCotError(input.endpointId, "update", error)))
482
- }
483
- : {}),
484
- sessionNamespace: input.definition.sessionNamespace,
485
- sleep,
486
- websocketClient: createLazyFeishuWebSocketClient(credentials, input.definition.baseUrl),
487
- workerConcurrency: 4
488
- });
489
- interactionRegistry.register(input.endpointId, interactions);
490
- return endpoint;
491
- },
492
- createRuntime: async (input: CreateRivusDeploymentRuntimeInput) => {
493
- const instanceState = join(STATE_DIR, "instances", input.instanceId);
494
- await mkdir(instanceState, { recursive: true });
495
- const eventLog = createJsonlAgentEventLog({ filePath: join(instanceState, "agent-events.jsonl") });
496
- const initialEvents = await Effect.runPromise(eventLog.readAll());
497
- const broker = createToolBroker({
498
- approvals: createRoutedHumanInteractionToolApprovalService(interactionRegistry),
499
- catalog: input.catalog,
500
- hostTools: [
501
- ...(input.definition.memory.tool ? [createRivusMemoryToolDescriptor({ memory })] : []),
502
- ...(backgroundService
503
- ? [
504
- ...createBackgroundSessionHostTools({
505
- createSessionId: () => `bg-${randomUUID()}`,
506
- definition: input.definition,
507
- service: backgroundService
508
- })
509
- ]
510
- : [])
511
- ],
512
- operations: await openJsonlToolOperationLedger({ filePath: join(instanceState, "tool-operations.jsonl") }),
513
- policy: { current: async () => ({ epoch: 1, revokedToolIds: [] }) }
514
- });
515
- const resolveToolName = createPiToolNameResolver(input.definition.tools);
516
- const skillRuntime = createPiSkillRuntime(input.definition.skills);
517
- const workingDirectory = input.projectSpace?.workingDirectory ?? process.cwd();
518
- const runContexts = management?.enabled
519
- ? management.createRunContexts({
520
- agentId: input.agentId,
521
- instanceId: input.instanceId,
522
- toolGrantSet: input.definition.toolGrantSet
523
- })
524
- : undefined;
525
- const workspaceRoot = input.projectSpace?.root ?? process.cwd();
526
- const workspaceInstructions = await createAgentsMdInstructionsProvider().resolve({
527
- maxBytes: 64 * 1024,
528
- workingDirectory: relative(workspaceRoot, workingDirectory) || ".",
529
- workspaceRoot: await createWorkspaceRootHandle(workspaceRoot)
530
- });
531
- const prepareProjectMemory =
532
- input.projectSpace && input.definition.memory.scopes.includes("project")
533
- ? createProjectMemoryPromptPreparer({
534
- agentId: input.agentId,
535
- memory,
536
- projectId: input.projectSpace.id
537
- })
538
- : undefined;
539
- const sessionRegistry = createPiSessionRegistry({
540
- createSession: async (firstInput) => {
541
- let activeInput = firstInput;
542
- const resources = await createPiSessionResources({
543
- agentDir: PI_AGENT_DIR,
544
- appendSystemPromptOverride: () =>
545
- [workspaceInstructions.content, skillRuntime.prompt].filter((content) => content.length > 0),
546
- cwd: workingDirectory,
547
- homeDirectory: homedir(),
548
- ...(input.projectSpace ? { projectSkillPaths: input.projectSpace.skillPaths } : {}),
549
- systemPromptOverride: () => input.definition.systemPrompt
550
- });
551
- const managedContext =
552
- management?.enabled && runContexts && input.definition.runtimeToolGrantSet.toolIds.includes("bash")
553
- ? createPiManagedSessionContext({
554
- ...(resources.bashToolOptions ? { bashToolOptions: resources.bashToolOptions } : {}),
555
- binDirectory: management.binDirectory,
556
- contexts: runContexts.registry,
557
- cwd: workingDirectory,
558
- nodeExecutable: process.execPath,
559
- socketPath: management.socketPath
560
- })
561
- : undefined;
562
- const bashTool = input.definition.runtimeToolGrantSet.toolIds.includes("bash")
563
- ? (managedContext?.tool ?? createPiBashTool(workingDirectory, resources.bashToolOptions))
564
- : undefined;
565
- const customTools = [
566
- ...(bashTool ? [bashTool] : []),
567
- ...createPiSkillReadTools({
568
- cwd: workingDirectory,
569
- runtimeToolIds: input.definition.runtimeToolGrantSet.toolIds,
570
- skillPaths: resources.skillNames.size > 0 ? resources.skillPaths : []
571
- }),
572
- ...createPiToolProxyDefinitions({
573
- agentId: input.agentId,
574
- approvals: createHumanInteractionToolApprovalGateway({ registry: interactionRegistry }),
575
- broker,
576
- getActiveInput: () => activeInput,
577
- instanceId: input.instanceId,
578
- memoryScopes: input.definition.memory.scopes,
579
- toolGrantSet: input.definition.toolGrantSet,
580
- tools: input.definition.tools
581
- }),
582
- ...(skillRuntime.tool ? [skillRuntime.tool] : [])
583
- ];
584
- const nativeToolIds = bashTool
585
- ? input.definition.runtimeToolGrantSet.toolIds.filter((toolId) => toolId !== "bash")
586
- : input.definition.runtimeToolGrantSet.toolIds;
587
- const activeToolNames = resolvePiSessionToolNames(nativeToolIds, customTools);
588
- const result = await createAgentSession(
589
- resources.withSessionOptions({
590
- ...piOptions,
591
- ...(management?.enabled ? management.runtime.getSessionOptions() : {}),
592
- customTools,
593
- excludeTools: [],
594
- sessionManager: SessionManager.create(workingDirectory, join(instanceState, "sessions")),
595
- tools: [...activeToolNames]
596
- })
597
- );
598
- return {
599
- activate: (loopInput) => {
600
- activeInput = loopInput;
601
- managedContext?.activate(loopInput);
602
- },
603
- deactivate: () => managedContext?.deactivate(),
604
- dispose: () => result.session.dispose(),
605
- preparePrompt: async (loopInput) => {
606
- validatePiSkillCommand(loopInput.text, resources.skillNames);
607
- return prepareProjectMemory ? prepareProjectMemory(loopInput) : loopInput.text;
608
- },
609
- resolveToolName,
610
- refreshResources: () => resources.refresh(),
611
- session: result.session
612
- };
613
- }
614
- });
615
- const unregisterModels = management?.enabled
616
- ? management.runtime.registerSessionRegistry(sessionRegistry)
617
- : undefined;
618
- const loop = createPiAgentLoop({
619
- supportsSteering: true,
620
- disposeSessionAfterRun: false,
621
- ...(management?.enabled ? { runBoundary: management.boundary } : {}),
622
- ...(telemetry ? { modelContentObserver: telemetry.modelContentObserver } : {}),
623
- resolveSession: (loopInput) => sessionRegistry.resolve(loopInput)
624
- });
625
- const scheduler = createSessionScheduler({
626
- createRuntime: (sessionKey) =>
627
- createAgentHarnessPooledRuntime(
628
- createAgentHarness({
629
- clock: createSystemClock(),
630
- eventSinks: telemetry ? [eventLog, telemetry.sink] : [eventLog],
631
- initialEvents: eventsForSession(initialEvents, sessionKey),
632
- loop,
633
- runIds: createUuidRunIds(),
634
- ...(input.binding.kind === "background-session" && backgroundSessionsConfig
635
- ? { runTimeoutMs: backgroundSessionsConfig.stepTimeoutMs }
636
- : {})
637
- })
638
- ),
639
- maxConcurrentSessions:
640
- input.binding.kind === "background-session" ? (backgroundSessionsConfig?.maxConcurrentSessions ?? 4) : 4,
641
- maxQueuedRuns: 32
642
- });
643
- return {
644
- ...scheduler,
645
- dispose: async () => {
646
- await scheduler.dispose?.();
647
- await sessionRegistry.disposeAll();
648
- unregisterModels?.();
649
- runContexts?.unregister();
650
- }
651
- };
652
- }
653
- };
654
- } catch (error) {
655
- await Promise.allSettled([management?.enabled ? management.close() : Promise.resolve(), telemetry?.shutdown()]);
656
- throw error;
657
- }
658
- }
659
-
660
- function eventsForSession<T extends { readonly runId: string; readonly sessionKey?: string }>(
661
- events: ReadonlyArray<T>,
662
- sessionKey: string
663
- ): ReadonlyArray<T> {
664
- const runIds = new Set(events.filter((event) => event.sessionKey === sessionKey).map((event) => event.runId));
665
- return events.filter((event) => runIds.has(event.runId));
666
- }
667
-
668
- function resolveAutomationTarget(targetRef: string, env: Readonly<Record<string, string | undefined>>): string {
669
- if (!targetRef.startsWith("env:")) throw new Error("Automation delivery targetRef must use env:<VARIABLE>");
670
- const variable = targetRef.slice("env:".length);
671
- if (!/^[A-Z][A-Z0-9_]*$/.test(variable)) throw new Error(`Invalid Automation delivery targetRef: ${targetRef}`);
672
- const target = env[variable]?.trim();
673
- if (!target) throw new Error(`${variable} is required`);
674
- return target;
675
- }
676
-
677
- function resolveExperimentalCotBaseUrl(env: Readonly<Record<string, string | undefined>>): string {
678
- const baseUrl = env.RIVUS_FEISHU_COT_BASE_URL?.trim();
679
- if (!baseUrl) {
680
- throw new Error("RIVUS_FEISHU_COT_BASE_URL is required when experimental.cotMessages is enabled");
681
- }
682
- return baseUrl;
683
- }
684
-
685
- function reportCotError(endpointId: string, operation: string, error: unknown): Effect.Effect<void> {
686
- return Effect.sync(() => {
687
- const detail = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
688
- console.error(`Experimental Feishu COT ${operation} failed for endpoint ${endpointId}: ${detail}`);
689
- });
690
- }
691
-
692
- function readAutomationRunResult(result: unknown): {
693
- readonly body?: string;
694
- readonly runId: string;
695
- readonly suppressedReason?: string;
696
- } {
697
- const { finalText, runId } = readStepRunResult(result);
698
- const trimmedText = finalText.trim();
699
- if (trimmedText.startsWith(AUTOMATION_SUPPRESSION_PREFIX)) {
700
- const reason = trimmedText.slice(AUTOMATION_SUPPRESSION_PREFIX.length).trim();
701
- if (!reason) throw new Error("Scheduled Automation suppression requires a reason");
702
- return { runId, suppressedReason: reason };
703
- }
704
- if (!trimmedText) throw new Error("Scheduled Automation Agent Run did not produce final text");
705
- return { body: finalText, runId };
706
- }
707
-
708
- function readStepRunResult(result: unknown): { readonly finalText: string; readonly runId: string } {
709
- if (
710
- result !== null &&
711
- typeof result === "object" &&
712
- "finalText" in result &&
713
- typeof result.finalText === "string" &&
714
- "runId" in result &&
715
- typeof result.runId === "string" &&
716
- result.runId.trim() !== ""
717
- ) {
718
- return { finalText: result.finalText, runId: result.runId };
719
- }
720
- throw new Error("Background session Agent Run did not produce a runId and final text");
721
- }
722
-
723
- function createLazyFeishuWebSocketClient(
724
- credentials: {
725
- readonly appId: string;
726
- readonly appSecret: string;
727
- },
728
- domain: string
729
- ): FeishuWebSocketClient {
730
- let client: Lark.WSClient | undefined;
731
- let connected = false;
732
- return {
733
- connected: () => connected,
734
- close: () => {
735
- client?.close();
736
- client = undefined;
737
- connected = false;
738
- },
739
- start: (options) =>
740
- new Promise<void>((resolve, reject) => {
741
- let settled = false;
742
- const settle = (callback: () => void) => {
743
- if (settled) return;
744
- settled = true;
745
- clearTimeout(timeout);
746
- callback();
747
- };
748
- const timeout = setTimeout(
749
- () => settle(() => reject(new Error("Feishu WebSocket handshake timed out after 15000ms"))),
750
- 15_000
751
- );
752
- client ??= new Lark.WSClient({
753
- ...credentials,
754
- domain,
755
- handshakeTimeoutMs: 10_000,
756
- onError: (error) => {
757
- connected = false;
758
- settle(() => reject(error));
759
- },
760
- onReady: () => {
761
- connected = true;
762
- settle(resolve);
763
- },
764
- onReconnected: () => {
765
- connected = true;
766
- },
767
- onReconnecting: () => {
768
- connected = false;
769
- }
770
- });
771
- void client.start(options as Parameters<Lark.WSClient["start"]>[0]).catch((error: unknown) => {
772
- connected = false;
773
- settle(() => reject(error));
774
- });
775
- })
776
- };
777
- }
778
-
779
- async function resolveFeishuBotOpenId(
780
- credentials: {
781
- readonly appId: string;
782
- readonly appSecret: string;
783
- },
784
- domain: string
785
- ): Promise<string> {
786
- const response = (await new Lark.Client({ ...credentials, domain }).request({
787
- method: "GET",
788
- url: "/open-apis/bot/v3/info"
789
- })) as { readonly bot?: { readonly open_id?: string }; readonly code?: number; readonly msg?: string };
790
- const openId = response.bot?.open_id;
791
- if (response.code !== 0 || !openId) {
792
- throw new Error(`Feishu bot identity lookup failed: ${response.msg ?? "response missing bot.open_id"}`);
793
- }
794
- return openId;
795
- }
796
-
797
- async function createPiSessionOptions(
798
- context: RivusDeploymentBootstrapContext,
799
- deferModel = false
800
- ): Promise<PiSessionOptions> {
801
- const modelReference = optional(context.env.PI_MODEL);
802
- const configuredModel = modelReference ? parseModelReference(modelReference) : undefined;
803
- const provider = configuredModel?.provider;
804
- const apiKey = await resolvePiApiKey(context.env);
805
- const baseUrl = optional(context.env.PI_BASE_URL);
806
- if ((apiKey || baseUrl) && !provider) {
807
- throw new Error("PI_API_KEY and PI_BASE_URL require PI_MODEL in provider/model form");
808
- }
809
- if (baseUrl && !deferModel) await writeProviderBaseUrlOverride(provider!, baseUrl);
810
- const modelRuntime = await ModelRuntime.create({
811
- allowModelNetwork: false,
812
- authPath: PI_AUTH_FILE,
813
- modelsPath: baseUrl || deferModel ? PI_MODELS_FILE : null
814
- });
815
- if (apiKey) await modelRuntime.setRuntimeApiKey(provider!, apiKey);
816
- const model =
817
- configuredModel && !deferModel
818
- ? modelRuntime.getModel(configuredModel.provider, configuredModel.modelId)
819
- : undefined;
820
- if (modelReference && !model && !deferModel)
821
- throw new Error(`PI_MODEL ${modelReference} was not found in the Pi model runtime`);
822
- const settings = deferModel
823
- ? SettingsManager.create(process.cwd(), PI_AGENT_DIR, { projectTrusted: false })
824
- : undefined;
825
- const thinkingLevel =
826
- readThinkingLevel(context.env.PI_THINKING_LEVEL) ??
827
- (configuredModel
828
- ? settings?.getModelThinkingLevel(configuredModel.provider, configuredModel.modelId)
829
- : undefined) ??
830
- settings?.getDefaultThinkingLevel();
831
- return {
832
- cwd: process.cwd(),
833
- modelRuntime,
834
- ...(model ? { model } : {}),
835
- ...(thinkingLevel ? { thinkingLevel } : {})
836
- };
837
- }
838
-
839
- function createEndpointConfig(
840
- definition: RivusEndpointDeployment,
841
- agentId: string,
842
- env: RivusDaemonEnv
843
- ): RivusDaemonConfig {
844
- return {
845
- agentId,
846
- feishu: {
847
- ...resolveFeishuEndpointCredentials(definition.credentialRef, env),
848
- baseUrl: definition.baseUrl,
849
- cardStreamLeaseMs: definition.cardStreamLeaseMs,
850
- streamMinIntervalMs: definition.streamMinIntervalMs
851
- },
852
- pi: {}
853
- };
854
- }
855
-
856
- async function resolvePiApiKey(env: Readonly<Record<string, string | undefined>>): Promise<string | undefined> {
857
- const inline = optional(env.PI_API_KEY);
858
- const filePath = optional(env.PI_API_KEY_FILE);
859
- if (inline && filePath) throw new Error("PI_API_KEY and PI_API_KEY_FILE cannot both be set");
860
- if (inline) return inline;
861
- if (!filePath) return undefined;
862
- const contents = optional(await readFile(filePath, "utf8"));
863
- if (!contents) throw new Error("PI_API_KEY_FILE must not be empty");
864
- return contents;
865
- }
866
-
867
- function optional(value: string | undefined): string | undefined {
868
- return value?.trim() || undefined;
869
- }
870
-
871
- function parseModelReference(reference: string): { readonly modelId: string; readonly provider: string } {
872
- const separator = reference.indexOf("/");
873
- if (separator <= 0 || separator === reference.length - 1) {
874
- throw new Error("PI_MODEL must use provider/model form, for example zai/glm-5.2");
875
- }
876
- return { modelId: reference.slice(separator + 1), provider: reference.slice(0, separator) };
877
- }
878
-
879
- function readThinkingLevel(value: string | undefined): RivusThinkingLevel | undefined {
880
- const level = optional(value);
881
- if (!level) return undefined;
882
- if (!THINKING_LEVELS.has(level as RivusThinkingLevel)) {
883
- throw new Error("PI_THINKING_LEVEL must be one of off, minimal, low, medium, high, xhigh");
884
- }
885
- return level as RivusThinkingLevel;
886
- }
887
-
888
- function sleep(ms: number): Effect.Effect<void> {
889
- return Effect.promise(() => new Promise((resolve) => setTimeout(resolve, ms)));
890
- }
891
-
892
- async function writeProviderBaseUrlOverride(provider: string, baseUrl: string): Promise<void> {
893
- await mergePiProviderBaseUrlOverride({ baseUrl, filePath: PI_MODELS_FILE, provider });
894
- }
1
+ // Projects initialized by Gateway keep the historical bootstrap specifier while
2
+ // the implementation remains in the Gateway package canonical owner.
3
+ export { createRivusDeploymentAdapters } from "@rivus/agent/bootstrap/pi-feishu";