@rivus/agent 0.14.4 → 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.
@@ -1,10 +1,13 @@
1
- import { mkdir, readFile } from "node:fs/promises";
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";
2
5
  import { createHash, randomUUID } from "node:crypto";
3
6
  import { homedir } from "node:os";
4
7
  import { join, relative } from "node:path";
5
8
  import { Effect } from "effect";
6
9
  import * as Lark from "@larksuiteoapi/node-sdk";
7
- import { createAgentSession, ModelRuntime, SessionManager } from "@earendil-works/pi-coding-agent";
10
+ import { createAgentSession, ModelRuntime, SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
8
11
  import {
9
12
  createAgentsMdInstructionsProvider,
10
13
  createAgentHarness,
@@ -29,6 +32,7 @@ import {
29
32
  createHumanInteractionEndpointRegistry,
30
33
  createHumanInteractionService,
31
34
  createHumanInteractionToolApprovalGateway,
35
+ createHumanInteractionModelChangeApproval,
32
36
  createJsonFetchRequest,
33
37
  createJsonFileFeishuCardTargetRegistry,
34
38
  createJsonlAgentEventLog,
@@ -70,6 +74,8 @@ import {
70
74
  type FeishuBackgroundSessionDelivery,
71
75
  type FeishuWebSocketClient,
72
76
  type RivusDaemonConfig,
77
+ type RivusDaemonEnv,
78
+ type RivusEndpointDeployment,
73
79
  type RivusDeploymentBackgroundSession,
74
80
  type RivusDeploymentBootstrapContext,
75
81
  type RivusThinkingLevel
@@ -94,9 +100,17 @@ const THINKING_LEVELS = new Set<RivusThinkingLevel>(["off", "minimal", "low", "m
94
100
 
95
101
  export async function createRivusDeploymentAdapters(context: RivusDeploymentBootstrapContext) {
96
102
  await mkdir(PI_AGENT_DIR, { recursive: true });
97
- const piOptions = await createPiSessionOptions(context);
103
+ const hasManagedState = await stat(join(STATE_DIR, "model-management", "model-state.json")).then(
104
+ () => true,
105
+ (error: NodeJS.ErrnoException) => {
106
+ if (error.code === "ENOENT") return false;
107
+ throw error;
108
+ }
109
+ );
110
+ const deferModel = context.env.RIVUS_MODEL_MANAGEMENT_ENABLED?.trim() === "true" || hasManagedState;
111
+ const piOptions = await createPiSessionOptions(context, deferModel);
98
112
  const telemetryConfig = resolveLangfuseTelemetryConfig(context.env);
99
- const telemetry = telemetryConfig ? createLangfuseAgentTelemetry(telemetryConfig) : undefined;
113
+ let telemetry: ReturnType<typeof createLangfuseAgentTelemetry> | undefined;
100
114
  const memory = await openJsonlAgentMemoryService({
101
115
  filePath: join(STATE_DIR, "memory", "agent-memory.jsonl")
102
116
  });
@@ -109,477 +123,526 @@ export async function createRivusDeploymentAdapters(context: RivusDeploymentBoot
109
123
  });
110
124
  const interactionRegistry = createHumanInteractionEndpointRegistry();
111
125
  const manifest = await loadRivusDeploymentManifest(context.manifestPath);
112
- const backgroundSessionsConfig = manifest.backgroundSessions;
113
- const sessionRepository = backgroundSessionsConfig?.enabled
114
- ? await openJsonlBackgroundSessionRepository({
115
- filePath: join(STATE_DIR, "background-sessions", "sessions.jsonl")
116
- })
117
- : undefined;
118
- const sessionDeliveries = backgroundSessionsConfig?.enabled
119
- ? await openJsonlBackgroundSessionDeliveryStore({
120
- filePath: join(STATE_DIR, "background-sessions", "deliveries.jsonl")
121
- })
122
- : undefined;
123
- const deliveryClients = new Map<string, FeishuBackgroundSessionDelivery>();
124
- const backgroundService = backgroundSessionsConfig?.enabled
125
- ? createBackgroundSessionService({
126
+ const defaultEndpoint = manifest.endpoints.find((endpoint) => endpoint.id === manifest.defaultEndpointId)!;
127
+ const management = await openPiModelManagementDeployment({
128
+ agentId: manifest.defaultAgentId,
129
+ approval: createHumanInteractionModelChangeApproval({
130
+ endpointId: manifest.defaultEndpointId,
131
+ registry: interactionRegistry
132
+ }),
133
+ agentCount: manifest.agents.length,
134
+ endpointId: manifest.defaultEndpointId,
135
+ env: context.env,
136
+ environmentOverrides: context.environmentOverrides ?? process.env,
137
+ ...(context.envFilePath ? { envFilePath: context.envFilePath } : {}),
138
+ modelAuthPath: PI_AUTH_FILE,
139
+ modelCatalogPath: PI_MODELS_FILE,
140
+ modelRuntime: piOptions.modelRuntime!,
141
+ reply: (messageId, text) => {
142
+ const config = createEndpointConfig(defaultEndpoint, manifest.defaultAgentId, context.env);
143
+ return createConfiguredFeishuTextReplySender({ config, client: createOpenApiClient(config) }).reply(
144
+ messageId,
145
+ text
146
+ );
147
+ },
148
+ stateDirectory: STATE_DIR,
149
+ thinkingLevel: piOptions.thinkingLevel ?? "medium"
150
+ });
151
+ try {
152
+ telemetry = telemetryConfig ? createLangfuseAgentTelemetry(telemetryConfig) : undefined;
153
+ if (management && !management.enabled) {
154
+ piOptions.model = await ensurePiModel({
155
+ modelRuntime: piOptions.modelRuntime!,
156
+ modelsPath: PI_MODELS_FILE,
157
+ target: management.model
158
+ });
159
+ }
160
+ if (management?.enabled && management.recoveryRequired) {
161
+ console.error("Model management requires recovery; business Runs are paused. Query rivus model status --json.");
162
+ }
163
+ if (management?.enabled && management.skillInstallation.status === "conflict") {
164
+ console.error("The runtime-management Skill has local edits; its installed instructions were preserved.");
165
+ }
166
+ const backgroundSessionsConfig = manifest.backgroundSessions;
167
+ const sessionRepository = backgroundSessionsConfig?.enabled
168
+ ? await openJsonlBackgroundSessionRepository({
169
+ filePath: join(STATE_DIR, "background-sessions", "sessions.jsonl")
170
+ })
171
+ : undefined;
172
+ const sessionDeliveries = backgroundSessionsConfig?.enabled
173
+ ? await openJsonlBackgroundSessionDeliveryStore({
174
+ filePath: join(STATE_DIR, "background-sessions", "deliveries.jsonl")
175
+ })
176
+ : undefined;
177
+ const deliveryClients = new Map<string, FeishuBackgroundSessionDelivery>();
178
+ const backgroundService = backgroundSessionsConfig?.enabled
179
+ ? createBackgroundSessionService({
180
+ clock: { now: () => new Date().toISOString() },
181
+ deliveries: sessionDeliveries!,
182
+ repository: sessionRepository!
183
+ })
184
+ : undefined;
185
+ const createBackgroundSessionAdapter = (
186
+ input: CreateRivusDeploymentBackgroundSessionInput
187
+ ): RivusDeploymentBackgroundSession => {
188
+ const resolveDeliverySender = async (endpointId: string): Promise<FeishuBackgroundSessionDelivery> => {
189
+ const existing = deliveryClients.get(endpointId);
190
+ if (existing) return existing;
191
+ const endpoint = manifest.endpoints.find(({ id }) => id === endpointId);
192
+ if (!endpoint) throw new Error(`background session delivery endpoint not found: ${endpointId}`);
193
+ const config = createEndpointConfig(endpoint, endpoint.agentId, context.env);
194
+ const sender = createConfiguredFeishuBackgroundSessionDelivery({
195
+ client: createOpenApiClient(config),
196
+ config
197
+ });
198
+ deliveryClients.set(endpointId, sender);
199
+ return sender;
200
+ };
201
+ const supervisor = createBackgroundSessionSupervisor({
126
202
  clock: { now: () => new Date().toISOString() },
203
+ config: {
204
+ intervalMs: resolveBackgroundSessionSupervisorIntervalMs(input.config.leaseMs),
205
+ leaseMs: input.config.leaseMs,
206
+ leaseRenewalIntervalMs: input.config.leaseRenewalIntervalMs,
207
+ maxConcurrentSessions: input.config.maxConcurrentSessions,
208
+ maxConsecutiveFailures: input.config.maxConsecutiveFailures,
209
+ retryBackoffMs: input.config.retryBackoffMs,
210
+ sessionLifetimeMs: input.config.sessionLifetimeMs
211
+ },
127
212
  deliveries: sessionDeliveries!,
128
- repository: sessionRepository!
129
- })
130
- : undefined;
131
- const createBackgroundSessionAdapter = (
132
- input: CreateRivusDeploymentBackgroundSessionInput
133
- ): RivusDeploymentBackgroundSession => {
134
- const resolveDeliverySender = async (endpointId: string): Promise<FeishuBackgroundSessionDelivery> => {
135
- const existing = deliveryClients.get(endpointId);
136
- if (existing) return existing;
137
- const endpoint = manifest.endpoints.find(({ id }) => id === endpointId);
138
- if (!endpoint) throw new Error(`background session delivery endpoint not found: ${endpointId}`);
139
- const credentials = resolveFeishuEndpointCredentials(endpoint.credentialRef, context.env);
140
- const config: RivusDaemonConfig = {
141
- agentId: endpoint.agentId,
142
- feishu: {
143
- ...credentials,
144
- baseUrl: endpoint.baseUrl,
145
- cardStreamLeaseMs: endpoint.cardStreamLeaseMs,
146
- streamMinIntervalMs: endpoint.streamMinIntervalMs
213
+ deliver: async (delivery) => {
214
+ const session = await sessionRepository!.get(delivery.sessionId);
215
+ if (!session) throw new Error(`background session not found for delivery: ${delivery.sessionId}`);
216
+ if (!session.origin.conversationId) {
217
+ throw new Error(`background session has no delivery conversation: ${delivery.sessionId}`);
218
+ }
219
+ const sender = await resolveDeliverySender(session.origin.endpointId);
220
+ return sender.deliver({
221
+ chatId: resolveFeishuDeliveryChatId(session.origin.conversationId),
222
+ deliveryId: delivery.deliveryId,
223
+ displayName: session.displayName,
224
+ kind: delivery.kind,
225
+ sessionId: session.sessionId,
226
+ text: delivery.text
227
+ });
147
228
  },
148
- pi: {}
149
- };
150
- const sender = createConfiguredFeishuBackgroundSessionDelivery({
151
- client: createOpenApiClient(config),
152
- config
229
+ onError: (error) => {
230
+ console.error("Background session supervisor failed", error);
231
+ },
232
+ repository: sessionRepository!,
233
+ runStep: async ({ session, signal, wakeText }) => {
234
+ let runId: string | undefined;
235
+ const invocation = {
236
+ allowedActorOpenIds: session.origin.allowedActorOpenIds,
237
+ ...(session.origin.conversationId ? { conversationId: session.origin.conversationId } : {}),
238
+ endpointId: session.origin.endpointId,
239
+ kind: "background-session" as const,
240
+ ...(session.origin.memory ? { memory: session.origin.memory } : {}),
241
+ sessionId: session.sessionId,
242
+ sourceMessageId: createBackgroundSessionStepSourceMessageId(session.sessionId, session.stepCount + 1),
243
+ tenantKey: session.origin.tenantKey
244
+ };
245
+ const abortPromise = new Promise<never>((_resolve, reject) => {
246
+ signal.addEventListener(
247
+ "abort",
248
+ () => {
249
+ if (runId) {
250
+ void input.cancel({
251
+ agentId: session.authority.agentId,
252
+ reason: "background session step aborted",
253
+ runId,
254
+ sessionKey: session.authority.sessionKey
255
+ });
256
+ }
257
+ reject(new Error("background session step aborted"));
258
+ },
259
+ { once: true }
260
+ );
261
+ });
262
+ const runPromise = input
263
+ .run({
264
+ agentId: session.authority.agentId,
265
+ invocation,
266
+ onUpdate: (update) => {
267
+ if (update.event.type === "agent_run_accepted" && !runId) {
268
+ runId = update.event.runId;
269
+ }
270
+ },
271
+ sessionKey: session.authority.sessionKey,
272
+ text: wakeText
273
+ })
274
+ .then((result) => readStepRunResult(result));
275
+ return Promise.race([runPromise, abortPromise]);
276
+ },
277
+ sleep
153
278
  });
154
- deliveryClients.set(endpointId, sender);
155
- return sender;
279
+ let running = false;
280
+ return {
281
+ running: () => running,
282
+ status: () => supervisor.status(),
283
+ start: async () => {
284
+ await Effect.runPromise(supervisor.recover());
285
+ await Effect.runPromise(supervisor.start());
286
+ running = true;
287
+ },
288
+ stop: async () => {
289
+ await Effect.runPromise(supervisor.stop());
290
+ running = false;
291
+ }
292
+ };
156
293
  };
157
- const supervisor = createBackgroundSessionSupervisor({
158
- clock: { now: () => new Date().toISOString() },
159
- config: {
160
- intervalMs: resolveBackgroundSessionSupervisorIntervalMs(input.config.leaseMs),
161
- leaseMs: input.config.leaseMs,
162
- leaseRenewalIntervalMs: input.config.leaseRenewalIntervalMs,
163
- maxConcurrentSessions: input.config.maxConcurrentSessions,
164
- maxConsecutiveFailures: input.config.maxConsecutiveFailures,
165
- retryBackoffMs: input.config.retryBackoffMs,
166
- sessionLifetimeMs: input.config.sessionLifetimeMs
294
+ return {
295
+ dispose: async () => {
296
+ if (management?.enabled) await management.close();
297
+ await telemetry?.shutdown();
167
298
  },
168
- deliveries: sessionDeliveries!,
169
- deliver: async (delivery) => {
170
- const session = await sessionRepository!.get(delivery.sessionId);
171
- if (!session) throw new Error(`background session not found for delivery: ${delivery.sessionId}`);
172
- if (!session.origin.conversationId) {
173
- throw new Error(`background session has no delivery conversation: ${delivery.sessionId}`);
174
- }
175
- const sender = await resolveDeliverySender(session.origin.endpointId);
176
- return sender.deliver({
177
- chatId: resolveFeishuDeliveryChatId(session.origin.conversationId),
178
- deliveryId: delivery.deliveryId,
179
- displayName: session.displayName,
180
- kind: delivery.kind,
181
- sessionId: session.sessionId,
182
- text: delivery.text
299
+ createRecoveryControl: () =>
300
+ openJsonlRecoveryControl({
301
+ endpointsDirectory: join(STATE_DIR, "endpoints"),
302
+ instancesDirectory: join(STATE_DIR, "instances")
303
+ }),
304
+ createBackgroundSession: backgroundSessionsConfig?.enabled
305
+ ? (input: CreateRivusDeploymentBackgroundSessionInput) => createBackgroundSessionAdapter(input)
306
+ : undefined,
307
+ createAutomation: async (input: CreateRivusDeploymentAutomationInput) => {
308
+ const config = createEndpointConfig(input.deliveryEndpoint, input.definition.agentId, context.env);
309
+ const openApiClient = createOpenApiClient(config);
310
+ const sender = createConfiguredFeishuAutomationCardSender({
311
+ client: openApiClient,
312
+ config
183
313
  });
184
- },
185
- onError: (error) => {
186
- console.error("Background session supervisor failed", error);
187
- },
188
- repository: sessionRepository!,
189
- runStep: async ({ session, signal, wakeText }) => {
190
- let runId: string | undefined;
191
- const invocation = {
192
- allowedActorOpenIds: session.origin.allowedActorOpenIds,
193
- ...(session.origin.conversationId ? { conversationId: session.origin.conversationId } : {}),
194
- endpointId: session.origin.endpointId,
195
- kind: "background-session" as const,
196
- ...(session.origin.memory ? { memory: session.origin.memory } : {}),
197
- sessionId: session.sessionId,
198
- sourceMessageId: createBackgroundSessionStepSourceMessageId(session.sessionId, session.stepCount + 1),
199
- tenantKey: session.origin.tenantKey
200
- };
201
- const abortPromise = new Promise<never>((_resolve, reject) => {
202
- signal.addEventListener(
203
- "abort",
204
- () => {
205
- if (runId) {
206
- void input.cancel({
207
- agentId: session.authority.agentId,
208
- reason: "background session step aborted",
209
- runId,
210
- sessionKey: session.authority.sessionKey
211
- });
212
- }
213
- reject(new Error("background session step aborted"));
214
- },
215
- { once: true }
216
- );
314
+ const repository = await openJsonAutomationTickRepository({
315
+ filePath: join(
316
+ STATE_DIR,
317
+ "automations",
318
+ createHash("sha256").update(input.automationId).digest("hex").slice(0, 16),
319
+ "ticks.json"
320
+ )
217
321
  });
218
- const runPromise = input
219
- .run({
220
- agentId: session.authority.agentId,
221
- invocation,
222
- onUpdate: (update) => {
223
- if (update.event.type === "agent_run_accepted" && !runId) {
224
- runId = update.event.runId;
225
- }
322
+ const target = resolveAutomationTarget(input.definition.delivery.targetRef, context.env);
323
+ const automation = createScheduledAutomation({
324
+ automationId: input.automationId,
325
+ binding: {
326
+ agentId: input.definition.agentId,
327
+ bindingId: input.automationId,
328
+ delivery: {
329
+ endpointId: input.definition.delivery.endpointId,
330
+ targetKey: createHash("sha256").update(target).digest("hex"),
331
+ targetType: input.definition.delivery.targetType
226
332
  },
227
- sessionKey: session.authority.sessionKey,
228
- text: wakeText
229
- })
230
- .then((result) => readStepRunResult(result));
231
- return Promise.race([runPromise, abortPromise]);
232
- },
233
- sleep
234
- });
235
- let running = false;
236
- return {
237
- running: () => running,
238
- status: () => supervisor.status(),
239
- start: async () => {
240
- await Effect.runPromise(supervisor.recover());
241
- await Effect.runPromise(supervisor.start());
242
- running = true;
243
- },
244
- stop: async () => {
245
- await Effect.runPromise(supervisor.stop());
246
- running = false;
247
- }
248
- };
249
- };
250
- return {
251
- dispose: () => telemetry?.shutdown(),
252
- createRecoveryControl: () =>
253
- openJsonlRecoveryControl({
254
- endpointsDirectory: join(STATE_DIR, "endpoints"),
255
- instancesDirectory: join(STATE_DIR, "instances")
256
- }),
257
- createBackgroundSession: backgroundSessionsConfig?.enabled
258
- ? (input: CreateRivusDeploymentBackgroundSessionInput) => createBackgroundSessionAdapter(input)
259
- : undefined,
260
- createAutomation: async (input: CreateRivusDeploymentAutomationInput) => {
261
- const credentials = resolveFeishuEndpointCredentials(input.deliveryEndpoint.credentialRef, context.env);
262
- const config: RivusDaemonConfig = {
263
- agentId: input.definition.agentId,
264
- feishu: {
265
- ...credentials,
266
- baseUrl: input.deliveryEndpoint.baseUrl,
267
- cardStreamLeaseMs: input.deliveryEndpoint.cardStreamLeaseMs,
268
- streamMinIntervalMs: input.deliveryEndpoint.streamMinIntervalMs
269
- },
270
- pi: {}
271
- };
272
- const openApiClient = createOpenApiClient(config);
273
- const sender = createConfiguredFeishuAutomationCardSender({
274
- client: openApiClient,
275
- config
276
- });
277
- const repository = await openJsonAutomationTickRepository({
278
- filePath: join(
279
- STATE_DIR,
280
- "automations",
281
- createHash("sha256").update(input.automationId).digest("hex").slice(0, 16),
282
- "ticks.json"
283
- )
284
- });
285
- const target = resolveAutomationTarget(input.definition.delivery.targetRef, context.env);
286
- const automation = createScheduledAutomation({
287
- automationId: input.automationId,
288
- binding: {
289
- agentId: input.definition.agentId,
290
- bindingId: input.automationId,
291
- delivery: {
292
- endpointId: input.definition.delivery.endpointId,
293
- targetKey: createHash("sha256").update(target).digest("hex"),
294
- targetType: input.definition.delivery.targetType
333
+ schedule: input.definition.schedule,
334
+ servicePrincipalId: `automation:${input.automationId}`,
335
+ skillGrantRevision: input.definition.runtimeDefinition.skillGrantSet.revision,
336
+ skillIds: input.definition.template.requestedSkillIds,
337
+ templateId: input.definition.templateId,
338
+ timeZone: input.definition.timeZone,
339
+ toolIds: input.definition.template.requestedToolIds
295
340
  },
296
- schedule: input.definition.schedule,
297
- servicePrincipalId: `automation:${input.automationId}`,
298
- skillGrantRevision: input.definition.runtimeDefinition.skillGrantSet.revision,
299
- skillIds: input.definition.template.requestedSkillIds,
300
- templateId: input.definition.templateId,
301
- timeZone: input.definition.timeZone,
302
- toolIds: input.definition.template.requestedToolIds
303
- },
304
- createInput: input.definition.template.createInput,
305
- deliver: ({ body, idempotencyKey, presentation }) =>
306
- Effect.runPromise(
307
- sender.send({
308
- idempotencyKey,
309
- receiveId: target,
310
- receiveIdType: input.definition.delivery.targetType,
311
- markdown: body,
312
- ...(presentation === undefined ? {} : { presentation })
313
- })
314
- ),
315
- onError: () => {
316
- console.error(`Scheduled Automation ${input.automationId} failed; it will retry`);
317
- },
318
- repository,
319
- run: async (runInput) => {
320
- const result = readAutomationRunResult(await input.run(runInput));
321
- const projected =
322
- result.body === undefined
323
- ? undefined
324
- : input.definition.template.createPresentation?.({
325
- occurrence: runInput.occurrence,
326
- text: result.body
327
- });
328
- const presentation = projected ? readAutomationPresentation(projected) : undefined;
329
- return { ...result, ...(presentation === undefined ? {} : { presentation }) };
330
- }
331
- });
332
- return automation;
333
- },
334
- createEndpoint: async (input: CreateRivusDeploymentEndpointInput) => {
335
- const credentials = resolveFeishuEndpointCredentials(input.definition.credentialRef, context.env);
336
- const botOpenId = await resolveFeishuBotOpenId(credentials, input.definition.baseUrl);
337
- const endpointState = join(STATE_DIR, "endpoints", input.endpointId);
338
- const cardTargets = createJsonFileFeishuCardTargetRegistry({
339
- filePath: join(endpointState, "feishu-card-targets.json")
340
- });
341
- const cardLedger = await openJsonlFeishuCardDeliveryLedger({
342
- filePath: join(endpointState, "feishu-card-delivery.jsonl")
343
- });
344
- const inbox = await openJsonlFeishuInboxRepository({ filePath: join(endpointState, "feishu-inbox.jsonl") });
345
- const sessionStore = await openJsonFeishuSessionStore({
346
- filePath: join(endpointState, "feishu-session-store.json")
347
- });
348
- const config: RivusDaemonConfig = {
349
- agentId: input.agentId,
350
- feishu: {
351
- ...credentials,
341
+ createInput: input.definition.template.createInput,
342
+ deliver: ({ body, idempotencyKey, presentation }) =>
343
+ Effect.runPromise(
344
+ sender.send({
345
+ idempotencyKey,
346
+ receiveId: target,
347
+ receiveIdType: input.definition.delivery.targetType,
348
+ markdown: body,
349
+ ...(presentation === undefined ? {} : { presentation })
350
+ })
351
+ ),
352
+ onError: () => {
353
+ console.error(`Scheduled Automation ${input.automationId} failed; it will retry`);
354
+ },
355
+ repository,
356
+ run: async (runInput) => {
357
+ const result = readAutomationRunResult(await input.run(runInput));
358
+ const projected =
359
+ result.body === undefined
360
+ ? undefined
361
+ : input.definition.template.createPresentation?.({
362
+ occurrence: runInput.occurrence,
363
+ text: result.body
364
+ });
365
+ const presentation = projected ? readAutomationPresentation(projected) : undefined;
366
+ return { ...result, ...(presentation === undefined ? {} : { presentation }) };
367
+ }
368
+ });
369
+ return automation;
370
+ },
371
+ createEndpoint: async (input: CreateRivusDeploymentEndpointInput) => {
372
+ const credentials = resolveFeishuEndpointCredentials(input.definition.credentialRef, context.env);
373
+ const botOpenId = await resolveFeishuBotOpenId(credentials, input.definition.baseUrl);
374
+ const endpointState = join(STATE_DIR, "endpoints", input.endpointId);
375
+ const cardTargets = createJsonFileFeishuCardTargetRegistry({
376
+ filePath: join(endpointState, "feishu-card-targets.json")
377
+ });
378
+ const cardLedger = await openJsonlFeishuCardDeliveryLedger({
379
+ filePath: join(endpointState, "feishu-card-delivery.jsonl")
380
+ });
381
+ const inbox = await openJsonlFeishuInboxRepository({ filePath: join(endpointState, "feishu-inbox.jsonl") });
382
+ const sessionStore = await openJsonFeishuSessionStore({
383
+ filePath: join(endpointState, "feishu-session-store.json")
384
+ });
385
+ const config = createEndpointConfig(input.definition, input.agentId, context.env);
386
+ const openApiClient = createOpenApiClient(config);
387
+ const promptContext = createFeishuTopicContextResolver({
352
388
  baseUrl: input.definition.baseUrl,
353
- cardStreamLeaseMs: input.definition.cardStreamLeaseMs,
354
- streamMinIntervalMs: input.definition.streamMinIntervalMs
355
- },
356
- pi: {}
357
- };
358
- const openApiClient = createOpenApiClient(config);
359
- const promptContext = createFeishuTopicContextResolver({
360
- baseUrl: input.definition.baseUrl,
361
- client: openApiClient
362
- });
363
- const cotPublisher = input.definition.experimental?.cotMessages
364
- ? createFeishuCotPublisher({
365
- baseUrl: resolveExperimentalCotBaseUrl(context.env),
389
+ client: openApiClient
390
+ });
391
+ const cotPublisher = input.definition.experimental?.cotMessages
392
+ ? createFeishuCotPublisher({
393
+ baseUrl: resolveExperimentalCotBaseUrl(context.env),
394
+ client: openApiClient,
395
+ minIntervalMs: input.definition.streamMinIntervalMs
396
+ })
397
+ : undefined;
398
+ const interactions = createHumanInteractionService({
399
+ clock: { now: () => new Date().toISOString() },
400
+ presenter: createConfiguredFeishuHumanInteractionPresenter({
366
401
  client: openApiClient,
367
- minIntervalMs: input.definition.streamMinIntervalMs
402
+ config
403
+ }),
404
+ repository: createJsonlHumanInteractionRepository({
405
+ filePath: join(endpointState, "human-interactions.jsonl")
368
406
  })
369
- : undefined;
370
- const interactions = createHumanInteractionService({
371
- clock: { now: () => new Date().toISOString() },
372
- presenter: createConfiguredFeishuHumanInteractionPresenter({
407
+ });
408
+ const cardRollover = createConfiguredFeishuCardRolloverRuntime({
409
+ agentName: input.agentId,
410
+ cardTargets,
373
411
  client: openApiClient,
374
- config
375
- }),
376
- repository: createJsonlHumanInteractionRepository({
377
- filePath: join(endpointState, "human-interactions.jsonl")
378
- })
379
- });
380
- const cardRollover = createConfiguredFeishuCardRolloverRuntime({
381
- agentName: input.agentId,
382
- cardTargets,
383
- client: openApiClient,
384
- clock: createSystemClock(),
385
- config,
386
- ledger: cardLedger,
387
- onError: (error) => {
388
- console.error(`Feishu card rollover failed for endpoint ${input.endpointId}`, error);
389
- },
390
- ...(input.definition.progressDisplay === undefined
391
- ? {}
392
- : { progressDisplay: input.definition.progressDisplay }),
393
- sleep,
394
- title: input.agentId
395
- });
396
- const publisher = cardRollover.rollover;
397
- const endpointEvents = createJsonlAgentEventLog({
398
- filePath: join(STATE_DIR, "instances", input.instanceId, "agent-events.jsonl")
399
- });
400
- const endpointInitialEvents = await Effect.runPromise(endpointEvents.readAll());
401
- await Effect.runPromise(
402
- createFeishuCardDeliveryReconciler({
403
- events: endpointInitialEvents,
412
+ clock: createSystemClock(),
413
+ config,
404
414
  ledger: cardLedger,
405
- publish: (action) => publisher.publish(action)
406
- }).reconcile()
407
- );
408
- const replies = createConfiguredFeishuTextReplySender({
409
- client: openApiClient,
410
- config
411
- });
412
- const prepareCardTarget = (run: FeishuAgentRunPreparation) => cardRollover.prepareRun(run);
413
- const endpoint = createFeishuDeploymentEndpoint({
414
- agentId: input.agentId,
415
- botOpenId,
416
- cardRollover: cardRollover.transport,
417
- finalizeRun: (runId) => cardRollover.rollover.releaseRun(runId),
418
- cancel: input.cancel,
419
- endpointId: input.endpointId,
420
- eventDispatcher: createLazyFeishuWebSocketEventDispatcher(() => new Lark.EventDispatcher({})),
421
- groupPolicy: input.definition.groupPolicy,
422
- handle: input.handle,
423
- inboxRepository: inbox,
424
- initialEvents: endpointInitialEvents,
425
- interactions,
426
- maxPendingMessages: 100,
427
- memoryTenantId,
428
- ...(input.projectSpaceId ? { projectSpaceId: input.projectSpaceId } : {}),
429
- onCapacityExceeded: (payload) =>
430
- replies.reply(payload.event.message.message_id, "Rivus is busy. Please retry in a moment."),
431
- prepareRun: createFeishuPresentationPreparation({
432
- ...(cotPublisher ? { cotPublisher } : {}),
433
- prepareCardTarget,
434
- reportCotError: (operation, error) => reportCotError(input.endpointId, operation, error)
435
- }),
436
- promptContext,
437
- publish: (action) => publisher.publish(action),
438
- reply: (messageId, text) => replies.reply(messageId, text),
439
- sessionStore,
440
- ...(input.steer ? { steer: input.steer } : {}),
441
- ...(cotPublisher
442
- ? {
443
- publishRunUpdate: (update) =>
444
- cotPublisher
445
- .publish(update)
446
- .pipe(Effect.catchAll((error) => reportCotError(input.endpointId, "update", error)))
447
- }
448
- : {}),
449
- sessionNamespace: input.definition.sessionNamespace,
450
- sleep,
451
- websocketClient: createLazyFeishuWebSocketClient(credentials, input.definition.baseUrl),
452
- workerConcurrency: 4
453
- });
454
- interactionRegistry.register(input.endpointId, interactions);
455
- return endpoint;
456
- },
457
- createRuntime: async (input: CreateRivusDeploymentRuntimeInput) => {
458
- const instanceState = join(STATE_DIR, "instances", input.instanceId);
459
- await mkdir(instanceState, { recursive: true });
460
- const eventLog = createJsonlAgentEventLog({ filePath: join(instanceState, "agent-events.jsonl") });
461
- const initialEvents = await Effect.runPromise(eventLog.readAll());
462
- const broker = createToolBroker({
463
- approvals: createRoutedHumanInteractionToolApprovalService(interactionRegistry),
464
- catalog: input.catalog,
465
- hostTools: [
466
- ...(input.definition.memory.tool ? [createRivusMemoryToolDescriptor({ memory })] : []),
467
- ...(backgroundService
468
- ? [
469
- ...createBackgroundSessionHostTools({
470
- createSessionId: () => `bg-${randomUUID()}`,
471
- definition: input.definition,
472
- service: backgroundService
473
- })
474
- ]
475
- : [])
476
- ],
477
- operations: await openJsonlToolOperationLedger({ filePath: join(instanceState, "tool-operations.jsonl") }),
478
- policy: { current: async () => ({ epoch: 1, revokedToolIds: [] }) }
479
- });
480
- const resolveToolName = createPiToolNameResolver(input.definition.tools);
481
- const skillRuntime = createPiSkillRuntime(input.definition.skills);
482
- const workingDirectory = input.projectSpace?.workingDirectory ?? process.cwd();
483
- const workspaceRoot = input.projectSpace?.root ?? process.cwd();
484
- const workspaceInstructions = await createAgentsMdInstructionsProvider().resolve({
485
- maxBytes: 64 * 1024,
486
- workingDirectory: relative(workspaceRoot, workingDirectory) || ".",
487
- workspaceRoot: await createWorkspaceRootHandle(workspaceRoot)
488
- });
489
- const prepareProjectMemory =
490
- input.projectSpace && input.definition.memory.scopes.includes("project")
491
- ? createProjectMemoryPromptPreparer({
415
+ onError: (error) => {
416
+ console.error(`Feishu card rollover failed for endpoint ${input.endpointId}`, error);
417
+ },
418
+ ...(input.definition.progressDisplay === undefined
419
+ ? {}
420
+ : { progressDisplay: input.definition.progressDisplay }),
421
+ sleep,
422
+ title: input.agentId
423
+ });
424
+ const publisher = cardRollover.rollover;
425
+ const endpointEvents = createJsonlAgentEventLog({
426
+ filePath: join(STATE_DIR, "instances", input.instanceId, "agent-events.jsonl")
427
+ });
428
+ const endpointInitialEvents = await Effect.runPromise(endpointEvents.readAll());
429
+ await Effect.runPromise(
430
+ createFeishuCardDeliveryReconciler({
431
+ events: endpointInitialEvents,
432
+ ledger: cardLedger,
433
+ publish: (action) => publisher.publish(action)
434
+ }).reconcile()
435
+ );
436
+ const replies = createConfiguredFeishuTextReplySender({
437
+ client: openApiClient,
438
+ config
439
+ });
440
+ const prepareCardTarget = (run: FeishuAgentRunPreparation) => cardRollover.prepareRun(run);
441
+ const endpoint = createFeishuDeploymentEndpoint({
442
+ agentId: input.agentId,
443
+ botOpenId,
444
+ cardRollover: cardRollover.transport,
445
+ finalizeRun: (runId) => cardRollover.rollover.releaseRun(runId),
446
+ cancel: input.cancel,
447
+ endpointId: input.endpointId,
448
+ eventDispatcher: createLazyFeishuWebSocketEventDispatcher(() => new Lark.EventDispatcher({})),
449
+ groupPolicy: input.definition.groupPolicy,
450
+ handle: input.handle,
451
+ inboxRepository: inbox,
452
+ initialEvents: endpointInitialEvents,
453
+ interactions,
454
+ maxPendingMessages: 100,
455
+ memoryTenantId,
456
+ ...(input.projectSpaceId ? { projectSpaceId: input.projectSpaceId } : {}),
457
+ onCapacityExceeded: (payload) =>
458
+ replies.reply(payload.event.message.message_id, "Rivus is busy. Please retry in a moment."),
459
+ prepareRun: createFeishuPresentationPreparation({
460
+ ...(cotPublisher ? { cotPublisher } : {}),
461
+ prepareCardTarget,
462
+ reportCotError: (operation, error) => reportCotError(input.endpointId, operation, error)
463
+ }),
464
+ promptContext,
465
+ publish: (action) => publisher.publish(action),
466
+ reply: (messageId, text) => replies.reply(messageId, text),
467
+ sessionStore,
468
+ ...(input.steer ? { steer: input.steer } : {}),
469
+ ...(cotPublisher
470
+ ? {
471
+ publishRunUpdate: (update) =>
472
+ cotPublisher
473
+ .publish(update)
474
+ .pipe(Effect.catchAll((error) => reportCotError(input.endpointId, "update", error)))
475
+ }
476
+ : {}),
477
+ sessionNamespace: input.definition.sessionNamespace,
478
+ sleep,
479
+ websocketClient: createLazyFeishuWebSocketClient(credentials, input.definition.baseUrl),
480
+ workerConcurrency: 4
481
+ });
482
+ interactionRegistry.register(input.endpointId, interactions);
483
+ return endpoint;
484
+ },
485
+ createRuntime: async (input: CreateRivusDeploymentRuntimeInput) => {
486
+ const instanceState = join(STATE_DIR, "instances", input.instanceId);
487
+ await mkdir(instanceState, { recursive: true });
488
+ const eventLog = createJsonlAgentEventLog({ filePath: join(instanceState, "agent-events.jsonl") });
489
+ const initialEvents = await Effect.runPromise(eventLog.readAll());
490
+ const broker = createToolBroker({
491
+ approvals: createRoutedHumanInteractionToolApprovalService(interactionRegistry),
492
+ catalog: input.catalog,
493
+ hostTools: [
494
+ ...(input.definition.memory.tool ? [createRivusMemoryToolDescriptor({ memory })] : []),
495
+ ...(backgroundService
496
+ ? [
497
+ ...createBackgroundSessionHostTools({
498
+ createSessionId: () => `bg-${randomUUID()}`,
499
+ definition: input.definition,
500
+ service: backgroundService
501
+ })
502
+ ]
503
+ : [])
504
+ ],
505
+ operations: await openJsonlToolOperationLedger({ filePath: join(instanceState, "tool-operations.jsonl") }),
506
+ policy: { current: async () => ({ epoch: 1, revokedToolIds: [] }) }
507
+ });
508
+ const resolveToolName = createPiToolNameResolver(input.definition.tools);
509
+ const skillRuntime = createPiSkillRuntime(input.definition.skills);
510
+ const workingDirectory = input.projectSpace?.workingDirectory ?? process.cwd();
511
+ const runContexts = management?.enabled
512
+ ? management.createRunContexts({
492
513
  agentId: input.agentId,
493
- memory,
494
- projectId: input.projectSpace.id
514
+ instanceId: input.instanceId,
515
+ toolGrantSet: input.definition.toolGrantSet
495
516
  })
496
517
  : undefined;
497
- const sessionRegistry = createPiSessionRegistry({
498
- createSession: async (firstInput) => {
499
- let activeInput = firstInput;
500
- const resources = await createPiSessionResources({
501
- agentDir: PI_AGENT_DIR,
502
- appendSystemPromptOverride: () =>
503
- [workspaceInstructions.content, skillRuntime.prompt].filter((content) => content.length > 0),
504
- cwd: workingDirectory,
505
- homeDirectory: homedir(),
506
- ...(input.projectSpace ? { projectSkillPaths: input.projectSpace.skillPaths } : {}),
507
- systemPromptOverride: () => input.definition.systemPrompt
508
- });
509
- const customTools = [
510
- ...createPiSkillReadTools({
518
+ const workspaceRoot = input.projectSpace?.root ?? process.cwd();
519
+ const workspaceInstructions = await createAgentsMdInstructionsProvider().resolve({
520
+ maxBytes: 64 * 1024,
521
+ workingDirectory: relative(workspaceRoot, workingDirectory) || ".",
522
+ workspaceRoot: await createWorkspaceRootHandle(workspaceRoot)
523
+ });
524
+ const prepareProjectMemory =
525
+ input.projectSpace && input.definition.memory.scopes.includes("project")
526
+ ? createProjectMemoryPromptPreparer({
527
+ agentId: input.agentId,
528
+ memory,
529
+ projectId: input.projectSpace.id
530
+ })
531
+ : undefined;
532
+ const sessionRegistry = createPiSessionRegistry({
533
+ createSession: async (firstInput) => {
534
+ let activeInput = firstInput;
535
+ const managedContext =
536
+ management?.enabled && runContexts && input.definition.runtimeToolGrantSet.toolIds.includes("bash")
537
+ ? createPiManagedSessionContext({
538
+ binDirectory: management.binDirectory,
539
+ contexts: runContexts.registry,
540
+ cwd: workingDirectory,
541
+ nodeExecutable: process.execPath,
542
+ socketPath: management.socketPath
543
+ })
544
+ : undefined;
545
+ const resources = await createPiSessionResources({
546
+ agentDir: PI_AGENT_DIR,
547
+ appendSystemPromptOverride: () =>
548
+ [workspaceInstructions.content, skillRuntime.prompt].filter((content) => content.length > 0),
511
549
  cwd: workingDirectory,
512
- runtimeToolIds: input.definition.runtimeToolGrantSet.toolIds,
513
- skillPaths: resources.skillNames.size > 0 ? resources.skillPaths : []
514
- }),
515
- ...createPiToolProxyDefinitions({
516
- agentId: input.agentId,
517
- approvals: createHumanInteractionToolApprovalGateway({ registry: interactionRegistry }),
518
- broker,
519
- getActiveInput: () => activeInput,
520
- instanceId: input.instanceId,
521
- memoryScopes: input.definition.memory.scopes,
522
- toolGrantSet: input.definition.toolGrantSet,
523
- tools: input.definition.tools
524
- }),
525
- ...(skillRuntime.tool ? [skillRuntime.tool] : [])
526
- ];
527
- const activeToolNames = resolvePiSessionToolNames(input.definition.runtimeToolGrantSet.toolIds, customTools);
528
- const result = await createAgentSession(
529
- resources.withSessionOptions({
530
- ...piOptions,
531
- customTools,
532
- excludeTools: [],
533
- sessionManager: SessionManager.create(workingDirectory, join(instanceState, "sessions")),
534
- tools: [...activeToolNames]
535
- })
536
- );
537
- return {
538
- activate: (loopInput) => {
539
- activeInput = loopInput;
540
- },
541
- dispose: () => result.session.dispose(),
542
- preparePrompt: async (loopInput) => {
543
- validatePiSkillCommand(loopInput.text, resources.skillNames);
544
- return prepareProjectMemory ? prepareProjectMemory(loopInput) : loopInput.text;
545
- },
546
- resolveToolName,
547
- session: result.session
548
- };
549
- }
550
- });
551
- const loop = createPiAgentLoop({
552
- disposeSessionAfterRun: false,
553
- ...(telemetry ? { modelContentObserver: telemetry.modelContentObserver } : {}),
554
- resolveSession: (loopInput) => sessionRegistry.resolve(loopInput)
555
- });
556
- const scheduler = createSessionScheduler({
557
- createRuntime: (sessionKey) =>
558
- createAgentHarnessPooledRuntime(
559
- createAgentHarness({
560
- clock: createSystemClock(),
561
- eventSinks: telemetry ? [eventLog, telemetry.sink] : [eventLog],
562
- initialEvents: eventsForSession(initialEvents, sessionKey),
563
- loop,
564
- runIds: createUuidRunIds(),
565
- ...(input.binding.kind === "background-session" && backgroundSessionsConfig
566
- ? { runTimeoutMs: backgroundSessionsConfig.stepTimeoutMs }
567
- : {})
568
- })
569
- ),
570
- maxConcurrentSessions:
571
- input.binding.kind === "background-session" ? (backgroundSessionsConfig?.maxConcurrentSessions ?? 4) : 4,
572
- maxQueuedRuns: 32
573
- });
574
- return {
575
- ...scheduler,
576
- dispose: async () => {
577
- await scheduler.dispose?.();
578
- await sessionRegistry.disposeAll();
579
- }
580
- };
581
- }
582
- };
550
+ homeDirectory: homedir(),
551
+ ...(input.projectSpace ? { projectSkillPaths: input.projectSpace.skillPaths } : {}),
552
+ systemPromptOverride: () => input.definition.systemPrompt
553
+ });
554
+ const customTools = [
555
+ ...(managedContext ? [managedContext.tool] : []),
556
+ ...createPiSkillReadTools({
557
+ cwd: workingDirectory,
558
+ runtimeToolIds: input.definition.runtimeToolGrantSet.toolIds,
559
+ skillPaths: resources.skillNames.size > 0 ? resources.skillPaths : []
560
+ }),
561
+ ...createPiToolProxyDefinitions({
562
+ agentId: input.agentId,
563
+ approvals: createHumanInteractionToolApprovalGateway({ registry: interactionRegistry }),
564
+ broker,
565
+ getActiveInput: () => activeInput,
566
+ instanceId: input.instanceId,
567
+ memoryScopes: input.definition.memory.scopes,
568
+ toolGrantSet: input.definition.toolGrantSet,
569
+ tools: input.definition.tools
570
+ }),
571
+ ...(skillRuntime.tool ? [skillRuntime.tool] : [])
572
+ ];
573
+ const nativeToolIds = managedContext
574
+ ? input.definition.runtimeToolGrantSet.toolIds.filter((toolId) => toolId !== "bash")
575
+ : input.definition.runtimeToolGrantSet.toolIds;
576
+ const activeToolNames = resolvePiSessionToolNames(nativeToolIds, customTools);
577
+ const result = await createAgentSession(
578
+ resources.withSessionOptions({
579
+ ...piOptions,
580
+ ...(management?.enabled ? management.runtime.getSessionOptions() : {}),
581
+ customTools,
582
+ excludeTools: [],
583
+ sessionManager: SessionManager.create(workingDirectory, join(instanceState, "sessions")),
584
+ tools: [...activeToolNames]
585
+ })
586
+ );
587
+ return {
588
+ activate: (loopInput) => {
589
+ activeInput = loopInput;
590
+ managedContext?.activate(loopInput);
591
+ },
592
+ deactivate: () => managedContext?.deactivate(),
593
+ dispose: () => result.session.dispose(),
594
+ preparePrompt: async (loopInput) => {
595
+ validatePiSkillCommand(loopInput.text, resources.skillNames);
596
+ return prepareProjectMemory ? prepareProjectMemory(loopInput) : loopInput.text;
597
+ },
598
+ resolveToolName,
599
+ refreshResources: () => resources.refresh(),
600
+ session: result.session
601
+ };
602
+ }
603
+ });
604
+ const unregisterModels = management?.enabled
605
+ ? management.runtime.registerSessionRegistry(sessionRegistry)
606
+ : undefined;
607
+ const loop = createPiAgentLoop({
608
+ disposeSessionAfterRun: false,
609
+ ...(management?.enabled ? { runBoundary: management.boundary } : {}),
610
+ ...(telemetry ? { modelContentObserver: telemetry.modelContentObserver } : {}),
611
+ resolveSession: (loopInput) => sessionRegistry.resolve(loopInput)
612
+ });
613
+ const scheduler = createSessionScheduler({
614
+ createRuntime: (sessionKey) =>
615
+ createAgentHarnessPooledRuntime(
616
+ createAgentHarness({
617
+ clock: createSystemClock(),
618
+ eventSinks: telemetry ? [eventLog, telemetry.sink] : [eventLog],
619
+ initialEvents: eventsForSession(initialEvents, sessionKey),
620
+ loop,
621
+ runIds: createUuidRunIds(),
622
+ ...(input.binding.kind === "background-session" && backgroundSessionsConfig
623
+ ? { runTimeoutMs: backgroundSessionsConfig.stepTimeoutMs }
624
+ : {})
625
+ })
626
+ ),
627
+ maxConcurrentSessions:
628
+ input.binding.kind === "background-session" ? (backgroundSessionsConfig?.maxConcurrentSessions ?? 4) : 4,
629
+ maxQueuedRuns: 32
630
+ });
631
+ return {
632
+ ...scheduler,
633
+ dispose: async () => {
634
+ await scheduler.dispose?.();
635
+ await sessionRegistry.disposeAll();
636
+ unregisterModels?.();
637
+ runContexts?.unregister();
638
+ }
639
+ };
640
+ }
641
+ };
642
+ } catch (error) {
643
+ await Promise.allSettled([management?.enabled ? management.close() : Promise.resolve(), telemetry?.shutdown()]);
644
+ throw error;
645
+ }
583
646
  }
584
647
 
585
648
  function eventsForSession<T extends { readonly runId: string; readonly sessionKey?: string }>(
@@ -719,7 +782,10 @@ async function resolveFeishuBotOpenId(
719
782
  return openId;
720
783
  }
721
784
 
722
- async function createPiSessionOptions(context: RivusDeploymentBootstrapContext): Promise<PiSessionOptions> {
785
+ async function createPiSessionOptions(
786
+ context: RivusDeploymentBootstrapContext,
787
+ deferModel = false
788
+ ): Promise<PiSessionOptions> {
723
789
  const modelReference = optional(context.env.PI_MODEL);
724
790
  const configuredModel = modelReference ? parseModelReference(modelReference) : undefined;
725
791
  const provider = configuredModel?.provider;
@@ -728,16 +794,28 @@ async function createPiSessionOptions(context: RivusDeploymentBootstrapContext):
728
794
  if ((apiKey || baseUrl) && !provider) {
729
795
  throw new Error("PI_API_KEY and PI_BASE_URL require PI_MODEL in provider/model form");
730
796
  }
731
- if (baseUrl) await writeProviderBaseUrlOverride(provider!, baseUrl);
797
+ if (baseUrl && !deferModel) await writeProviderBaseUrlOverride(provider!, baseUrl);
732
798
  const modelRuntime = await ModelRuntime.create({
733
799
  allowModelNetwork: false,
734
800
  authPath: PI_AUTH_FILE,
735
- modelsPath: baseUrl ? PI_MODELS_FILE : null
801
+ modelsPath: baseUrl || deferModel ? PI_MODELS_FILE : null
736
802
  });
737
803
  if (apiKey) await modelRuntime.setRuntimeApiKey(provider!, apiKey);
738
- const model = configuredModel ? modelRuntime.getModel(configuredModel.provider, configuredModel.modelId) : undefined;
739
- if (modelReference && !model) throw new Error(`PI_MODEL ${modelReference} was not found in the Pi model runtime`);
740
- const thinkingLevel = readThinkingLevel(context.env.PI_THINKING_LEVEL);
804
+ const model =
805
+ configuredModel && !deferModel
806
+ ? modelRuntime.getModel(configuredModel.provider, configuredModel.modelId)
807
+ : undefined;
808
+ if (modelReference && !model && !deferModel)
809
+ throw new Error(`PI_MODEL ${modelReference} was not found in the Pi model runtime`);
810
+ const settings = deferModel
811
+ ? SettingsManager.create(process.cwd(), PI_AGENT_DIR, { projectTrusted: false })
812
+ : undefined;
813
+ const thinkingLevel =
814
+ readThinkingLevel(context.env.PI_THINKING_LEVEL) ??
815
+ (configuredModel
816
+ ? settings?.getModelThinkingLevel(configuredModel.provider, configuredModel.modelId)
817
+ : undefined) ??
818
+ settings?.getDefaultThinkingLevel();
741
819
  return {
742
820
  cwd: process.cwd(),
743
821
  modelRuntime,
@@ -746,6 +824,23 @@ async function createPiSessionOptions(context: RivusDeploymentBootstrapContext):
746
824
  };
747
825
  }
748
826
 
827
+ function createEndpointConfig(
828
+ definition: RivusEndpointDeployment,
829
+ agentId: string,
830
+ env: RivusDaemonEnv
831
+ ): RivusDaemonConfig {
832
+ return {
833
+ agentId,
834
+ feishu: {
835
+ ...resolveFeishuEndpointCredentials(definition.credentialRef, env),
836
+ baseUrl: definition.baseUrl,
837
+ cardStreamLeaseMs: definition.cardStreamLeaseMs,
838
+ streamMinIntervalMs: definition.streamMinIntervalMs
839
+ },
840
+ pi: {}
841
+ };
842
+ }
843
+
749
844
  async function resolvePiApiKey(env: Readonly<Record<string, string | undefined>>): Promise<string | undefined> {
750
845
  const inline = optional(env.PI_API_KEY);
751
846
  const filePath = optional(env.PI_API_KEY_FILE);