@rivus/agent 0.5.0 → 0.5.2

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.
package/dist/acp.js CHANGED
@@ -312,7 +312,11 @@ function createAcpStdioAgentLoop(options) {
312
312
  resolveSession: async (input) => {
313
313
  const processConnection = await resolveConnection();
314
314
  const current = processConnection.sessions.get(input.sessionKey);
315
- if (current) return current;
315
+ if (current?.isReusable) return current;
316
+ if (current) {
317
+ current.dispose();
318
+ processConnection.sessions.delete(input.sessionKey);
319
+ }
316
320
  const session = new SdkAcpAgentSession(await processConnection.connection.agent.buildSession(options.workingDirectory).start(), processConnection.connection.agent);
317
321
  sessionKeys.set(session.sessionId, input.sessionKey);
318
322
  processConnection.sessions.set(input.sessionKey, session);
@@ -324,6 +328,7 @@ function createAcpStdioAgentLoop(options) {
324
328
  var SdkAcpAgentSession = class {
325
329
  session;
326
330
  agent;
331
+ reusable = true;
327
332
  constructor(session, agent) {
328
333
  this.session = session;
329
334
  this.agent = agent;
@@ -331,10 +336,15 @@ var SdkAcpAgentSession = class {
331
336
  get sessionId() {
332
337
  return this.session.sessionId;
333
338
  }
339
+ get isReusable() {
340
+ return this.reusable;
341
+ }
334
342
  cancel() {
343
+ this.reusable = false;
335
344
  return this.agent.notify(acp.methods.agent.session.cancel, { sessionId: this.session.sessionId });
336
345
  }
337
346
  dispose() {
347
+ this.reusable = false;
338
348
  this.session.dispose();
339
349
  }
340
350
  async prompt(text, onUpdate) {
package/dist/index.d.ts CHANGED
@@ -18,6 +18,7 @@ interface AgentHarnessOptions {
18
18
  readonly initialEvents?: ReadonlyArray<AgentDomainEvent>;
19
19
  readonly initialRunStates?: ReadonlyArray<AgentRunState>;
20
20
  readonly loop: AgentLoop;
21
+ readonly runTimeoutMs?: number;
21
22
  readonly runIds: RunIdGenerator;
22
23
  }
23
24
  interface PromptCommand {
@@ -1902,14 +1903,17 @@ declare function createFeishuEventHandlers(options: FeishuEventHandlersOptions):
1902
1903
  //#region src/composition/feishu-agent-runtime.d.ts
1903
1904
  interface FeishuAgentRuntimeOptions {
1904
1905
  readonly agentId: string;
1906
+ readonly botOpenId?: string;
1905
1907
  readonly clock: AgentClock;
1906
1908
  readonly eventSinks?: ReadonlyArray<AgentDomainEventSink>;
1909
+ readonly inboxRepository?: FeishuInboxRepository;
1907
1910
  readonly initialEvents?: ReadonlyArray<AgentDomainEvent>;
1908
1911
  readonly initialRunStates?: ReadonlyArray<AgentRunState>;
1909
1912
  readonly loop: AgentLoop;
1910
1913
  readonly prepareRun?: (run: FeishuAgentRunPreparation) => Effect.Effect<void, unknown>;
1911
1914
  readonly periodicFlush?: FeishuPeriodicFlushSupervisor;
1912
1915
  readonly publish: (action: FeishuStreamAction) => Effect.Effect<void, unknown>;
1916
+ readonly runTimeoutMs?: number;
1913
1917
  readonly runIds: RunIdGenerator;
1914
1918
  }
1915
1919
  interface FeishuPeriodicFlushSupervisor {
@@ -2155,6 +2159,7 @@ declare function createJsonlAgentEventLog(options: JsonlAgentEventLogOptions): A
2155
2159
  type ConfiguredRivusDaemonBootstrapRequest = ConfiguredFeishuOpenApiRequest;
2156
2160
  type ConfiguredRivusDaemonBootstrapResponse = ConfiguredFeishuOpenApiResponse;
2157
2161
  interface ConfiguredRivusDaemonBootstrapOptions {
2162
+ readonly botOpenId?: string;
2158
2163
  readonly cardTargets: FeishuCardTargetRegistry;
2159
2164
  readonly clock: AgentClock;
2160
2165
  readonly config: RivusDaemonConfig;
@@ -2164,10 +2169,12 @@ interface ConfiguredRivusDaemonBootstrapOptions {
2164
2169
  readonly flushIntervalMs?: number;
2165
2170
  readonly initialEvents?: ReadonlyArray<AgentDomainEvent>;
2166
2171
  readonly initialRunStates?: ReadonlyArray<AgentRunState>;
2172
+ readonly inboxRepository?: FeishuInboxRepository;
2167
2173
  readonly loop: AgentLoop;
2168
2174
  readonly onWorkerError?: (error: unknown) => void | Promise<void>;
2169
2175
  readonly request: (request: ConfiguredRivusDaemonBootstrapRequest) => Effect.Effect<ConfiguredRivusDaemonBootstrapResponse, unknown>;
2170
2176
  readonly runIds: RunIdGenerator;
2177
+ readonly runTimeoutMs?: number;
2171
2178
  readonly sleep: (ms: number) => Effect.Effect<void, unknown>;
2172
2179
  readonly websocketClient: FeishuWebSocketClient;
2173
2180
  readonly workerIntervalMs?: number;
package/dist/index.js CHANGED
@@ -1083,6 +1083,7 @@ function createAgentRunUpdateHandler(handle) {
1083
1083
  });
1084
1084
  }
1085
1085
  function createAgentHarness(options) {
1086
+ if (options.runTimeoutMs !== void 0 && (!Number.isSafeInteger(options.runTimeoutMs) || options.runTimeoutMs < 1)) throw new Error("Agent run timeout must be a positive integer");
1086
1087
  let activeRun;
1087
1088
  let activeCancellation;
1088
1089
  let activeRunState;
@@ -1223,7 +1224,7 @@ function createAgentHarness(options) {
1223
1224
  sessionKey: command.sessionKey,
1224
1225
  text: command.text
1225
1226
  };
1226
- yield* Effect.try({
1227
+ const loop = Effect.try({
1227
1228
  try: () => options.loop.run(loopInput),
1228
1229
  catch: (error) => error
1229
1230
  }).pipe(Effect.flatMap((loopStream) => Stream.runForEach(loopStream.pipe(Stream.interruptWhenDeferred(cancellation.deferred)), (event) => Effect.gen(function* () {
@@ -1244,6 +1245,16 @@ function createAgentHarness(options) {
1244
1245
  return yield* Effect.fail(new AgentLoopFailed(runId, errorMessage, error, state));
1245
1246
  });
1246
1247
  }));
1248
+ yield* options.runTimeoutMs === void 0 ? loop : Effect.raceFirst(loop, Effect.sleep(options.runTimeoutMs).pipe(Effect.flatMap(() => {
1249
+ const request = {
1250
+ reason: `run timed out after ${options.runTimeoutMs}ms`,
1251
+ runId,
1252
+ sessionKey: command.sessionKey
1253
+ };
1254
+ cancellation.requested = request;
1255
+ cancellation.abortController.abort(request);
1256
+ return Deferred.succeed(cancellation.deferred, request);
1257
+ }), Effect.asVoid));
1247
1258
  if (cancellation.requested) {
1248
1259
  const cancelledAt = yield* options.clock.now;
1249
1260
  yield* record({
@@ -2626,10 +2637,12 @@ function createFeishuAgentRuntime(options) {
2626
2637
  ...options.initialEvents ? { initialEvents: options.initialEvents } : {},
2627
2638
  ...options.initialRunStates ? { initialRunStates: options.initialRunStates } : {},
2628
2639
  loop: options.loop,
2640
+ ...options.runTimeoutMs === void 0 ? {} : { runTimeoutMs: options.runTimeoutMs },
2629
2641
  runIds: options.runIds
2630
2642
  });
2631
2643
  const daemon = createFeishuAgentDaemon({
2632
2644
  agentId: options.agentId,
2645
+ ...options.botOpenId ? { botOpenId: options.botOpenId } : {},
2633
2646
  harness,
2634
2647
  ...options.prepareRun ? { prepareRun: options.prepareRun } : {},
2635
2648
  publish: options.publish
@@ -2638,7 +2651,10 @@ function createFeishuAgentRuntime(options) {
2638
2651
  let lastHandled;
2639
2652
  const queue = createFeishuMessageQueue({
2640
2653
  handleMessage: (payload, handleOptions) => {
2641
- const effect = daemon.handleMessage(payload, handleOptions).pipe(Effect.tap((result) => describeFeishuMessageIntake(payload, { agentId: options.agentId }).pipe(Effect.tap((intake) => Effect.gen(function* () {
2654
+ const effect = daemon.handleMessage(payload, handleOptions).pipe(Effect.tap((result) => describeFeishuMessageIntake(payload, {
2655
+ agentId: options.agentId,
2656
+ ...options.botOpenId ? { botOpenId: options.botOpenId } : {}
2657
+ }).pipe(Effect.tap((intake) => Effect.gen(function* () {
2642
2658
  const observedAt = yield* options.clock.now;
2643
2659
  lastHandled = {
2644
2660
  intake,
@@ -2649,6 +2665,7 @@ function createFeishuAgentRuntime(options) {
2649
2665
  })))));
2650
2666
  return options.periodicFlush ? options.periodicFlush.withPeriodicFlush(effect) : effect;
2651
2667
  },
2668
+ ...options.inboxRepository ? { repository: options.inboxRepository } : {},
2652
2669
  shouldRetryError: isRetryableFeishuMessageError
2653
2670
  });
2654
2671
  const worker = createFeishuMessageWorker({ queue });
@@ -2675,7 +2692,10 @@ function createFeishuAgentRuntime(options) {
2675
2692
  ...lastHandled ? { lastHandled } : {}
2676
2693
  }),
2677
2694
  replayReceiveMessage: (payload, replayOptions) => Effect.gen(function* () {
2678
- const intake = yield* describeFeishuMessageIntake(payload, { agentId: options.agentId });
2695
+ const intake = yield* describeFeishuMessageIntake(payload, {
2696
+ agentId: options.agentId,
2697
+ ...options.botOpenId ? { botOpenId: options.botOpenId } : {}
2698
+ });
2679
2699
  return {
2680
2700
  accepted: yield* observedQueue.accept(payload, replayOptions),
2681
2701
  drained: yield* worker.drainAvailable(),
@@ -2818,25 +2838,113 @@ function resolveRunPresentation(input) {
2818
2838
  }
2819
2839
  }
2820
2840
  //#endregion
2841
+ //#region src/infrastructure/feishu/feishu-tenant-token-provider.ts
2842
+ var FeishuTenantAccessTokenError = class {
2843
+ status;
2844
+ code;
2845
+ message;
2846
+ _tag = "FeishuTenantAccessTokenError";
2847
+ constructor(status, code, message) {
2848
+ this.status = status;
2849
+ this.code = code;
2850
+ this.message = message;
2851
+ }
2852
+ };
2853
+ const DEFAULT_REFRESH_WINDOW_MS = 1800 * 1e3;
2854
+ function createFeishuTenantAccessTokenProvider(options) {
2855
+ const baseUrl = (options.baseUrl ?? "https://open.feishu.cn").replace(/\/$/, "");
2856
+ const now = options.now ?? Date.now;
2857
+ const refreshWindowMs = options.refreshWindowMs ?? DEFAULT_REFRESH_WINDOW_MS;
2858
+ let cached;
2859
+ return { getTenantAccessToken: () => Effect.gen(function* () {
2860
+ if (cached && now() < cached.refreshAfterMs) return cached.token;
2861
+ const response = yield* options.request({
2862
+ body: {
2863
+ app_id: options.appId,
2864
+ app_secret: options.appSecret
2865
+ },
2866
+ headers: { "Content-Type": "application/json; charset=utf-8" },
2867
+ method: "POST",
2868
+ url: `${baseUrl}/open-apis/auth/v3/tenant_access_token/internal`
2869
+ });
2870
+ if (response.status < 200 || response.status >= 300 || response.body.code !== 0 || !response.body.tenant_access_token || typeof response.body.expire !== "number") return yield* Effect.fail(new FeishuTenantAccessTokenError(response.status, response.body.code, response.body.msg ?? "Failed to get tenant_access_token"));
2871
+ cached = {
2872
+ refreshAfterMs: now() + response.body.expire * 1e3 - refreshWindowMs,
2873
+ token: response.body.tenant_access_token
2874
+ };
2875
+ return cached.token;
2876
+ }) };
2877
+ }
2878
+ //#endregion
2879
+ //#region src/infrastructure/feishu/feishu-openapi-client.ts
2880
+ var FeishuOpenApiError = class extends Error {
2881
+ status;
2882
+ code;
2883
+ _tag = "FeishuOpenApiError";
2884
+ name = "FeishuOpenApiError";
2885
+ constructor(status, code, message) {
2886
+ super(message);
2887
+ this.status = status;
2888
+ this.code = code;
2889
+ }
2890
+ };
2891
+ function createFeishuOpenApiClient(options) {
2892
+ return { request: (request, errorMessage = "Feishu OpenAPI error") => Effect.gen(function* () {
2893
+ const token = yield* options.getTenantAccessToken();
2894
+ const response = yield* options.request({
2895
+ ...request,
2896
+ headers: {
2897
+ Authorization: `Bearer ${token}`,
2898
+ "Content-Type": "application/json; charset=utf-8"
2899
+ }
2900
+ });
2901
+ if (response.status < 200 || response.status >= 300 || response.body.code !== 0) return yield* Effect.fail(new FeishuOpenApiError(response.status, response.body.code, response.body.msg ?? errorMessage));
2902
+ return response;
2903
+ }) };
2904
+ }
2905
+ function createConfiguredFeishuOpenApiClient(options) {
2906
+ const tokenProvider = createFeishuTenantAccessTokenProvider({
2907
+ appId: options.config.feishu.appId,
2908
+ appSecret: options.config.feishu.appSecret,
2909
+ baseUrl: options.config.feishu.baseUrl,
2910
+ request: (request) => options.request(request).pipe(Effect.map((response) => response))
2911
+ });
2912
+ return createFeishuOpenApiClient({
2913
+ getTenantAccessToken: () => tokenProvider.getTenantAccessToken(),
2914
+ request: (request) => options.request(request).pipe(Effect.map((response) => response))
2915
+ });
2916
+ }
2917
+ //#endregion
2821
2918
  //#region src/infrastructure/feishu/feishu-cardkit-openapi-client.ts
2822
2919
  function createFeishuCardKitOpenApiClient(options) {
2823
2920
  const baseUrl = (options.baseUrl ?? "https://open.feishu.cn").replace(/\/$/, "");
2824
2921
  const agentName = options.agentName ?? "Rivus Agent";
2922
+ const streamingClosedCards = /* @__PURE__ */ new Set();
2825
2923
  const call = (request) => options.client.request(request).pipe(Effect.asVoid);
2924
+ const updateTerminal = (request, status) => updateTerminalCard(baseUrl, agentName, request, status, call).pipe(Effect.tap(() => Effect.sync(() => streamingClosedCards.delete(request.cardId))));
2826
2925
  return {
2827
- cancel: (request) => updateTerminalCard(baseUrl, agentName, request, "cancelled", call),
2828
- fail: (request) => updateTerminalCard(baseUrl, agentName, request, "failed", call),
2829
- finish: (request) => updateTerminalCard(baseUrl, agentName, request, "completed", call),
2830
- updateText: (request) => call({
2831
- body: {
2832
- content: request.content,
2833
- sequence: request.sequence
2834
- },
2835
- method: "PUT",
2836
- url: `${baseUrl}/open-apis/cardkit/v1/cards/${encodeURIComponent(request.cardId)}/elements/${encodeURIComponent(request.elementId)}/content`
2837
- })
2926
+ cancel: (request) => updateTerminal(request, "cancelled"),
2927
+ fail: (request) => updateTerminal(request, "failed"),
2928
+ finish: (request) => updateTerminal(request, "completed"),
2929
+ updateText: (request) => {
2930
+ if (streamingClosedCards.has(request.cardId)) return Effect.void;
2931
+ return call({
2932
+ body: {
2933
+ content: request.content,
2934
+ sequence: request.sequence
2935
+ },
2936
+ method: "PUT",
2937
+ url: `${baseUrl}/open-apis/cardkit/v1/cards/${encodeURIComponent(request.cardId)}/elements/${encodeURIComponent(request.elementId)}/content`
2938
+ }).pipe(Effect.catchAll((error) => {
2939
+ if (!isStreamingClosed(error)) return Effect.fail(error);
2940
+ return Effect.sync(() => streamingClosedCards.add(request.cardId));
2941
+ }));
2942
+ }
2838
2943
  };
2839
2944
  }
2945
+ function isStreamingClosed(error) {
2946
+ return error instanceof FeishuOpenApiError && (error.code === 200850 || error.code === 300309);
2947
+ }
2840
2948
  function updateTerminalCard(baseUrl, agentName, request, status, call) {
2841
2949
  const card = status === "completed" && "text" in request ? createFeishuAgentRunCard({
2842
2950
  agentName,
@@ -2968,83 +3076,6 @@ function createConfiguredFeishuCardKitPublisher(options) {
2968
3076
  }) });
2969
3077
  }
2970
3078
  //#endregion
2971
- //#region src/infrastructure/feishu/feishu-tenant-token-provider.ts
2972
- var FeishuTenantAccessTokenError = class {
2973
- status;
2974
- code;
2975
- message;
2976
- _tag = "FeishuTenantAccessTokenError";
2977
- constructor(status, code, message) {
2978
- this.status = status;
2979
- this.code = code;
2980
- this.message = message;
2981
- }
2982
- };
2983
- const DEFAULT_REFRESH_WINDOW_MS = 1800 * 1e3;
2984
- function createFeishuTenantAccessTokenProvider(options) {
2985
- const baseUrl = (options.baseUrl ?? "https://open.feishu.cn").replace(/\/$/, "");
2986
- const now = options.now ?? Date.now;
2987
- const refreshWindowMs = options.refreshWindowMs ?? DEFAULT_REFRESH_WINDOW_MS;
2988
- let cached;
2989
- return { getTenantAccessToken: () => Effect.gen(function* () {
2990
- if (cached && now() < cached.refreshAfterMs) return cached.token;
2991
- const response = yield* options.request({
2992
- body: {
2993
- app_id: options.appId,
2994
- app_secret: options.appSecret
2995
- },
2996
- headers: { "Content-Type": "application/json; charset=utf-8" },
2997
- method: "POST",
2998
- url: `${baseUrl}/open-apis/auth/v3/tenant_access_token/internal`
2999
- });
3000
- if (response.status < 200 || response.status >= 300 || response.body.code !== 0 || !response.body.tenant_access_token || typeof response.body.expire !== "number") return yield* Effect.fail(new FeishuTenantAccessTokenError(response.status, response.body.code, response.body.msg ?? "Failed to get tenant_access_token"));
3001
- cached = {
3002
- refreshAfterMs: now() + response.body.expire * 1e3 - refreshWindowMs,
3003
- token: response.body.tenant_access_token
3004
- };
3005
- return cached.token;
3006
- }) };
3007
- }
3008
- //#endregion
3009
- //#region src/infrastructure/feishu/feishu-openapi-client.ts
3010
- var FeishuOpenApiError = class extends Error {
3011
- status;
3012
- code;
3013
- _tag = "FeishuOpenApiError";
3014
- name = "FeishuOpenApiError";
3015
- constructor(status, code, message) {
3016
- super(message);
3017
- this.status = status;
3018
- this.code = code;
3019
- }
3020
- };
3021
- function createFeishuOpenApiClient(options) {
3022
- return { request: (request, errorMessage = "Feishu OpenAPI error") => Effect.gen(function* () {
3023
- const token = yield* options.getTenantAccessToken();
3024
- const response = yield* options.request({
3025
- ...request,
3026
- headers: {
3027
- Authorization: `Bearer ${token}`,
3028
- "Content-Type": "application/json; charset=utf-8"
3029
- }
3030
- });
3031
- if (response.status < 200 || response.status >= 300 || response.body.code !== 0) return yield* Effect.fail(new FeishuOpenApiError(response.status, response.body.code, response.body.msg ?? errorMessage));
3032
- return response;
3033
- }) };
3034
- }
3035
- function createConfiguredFeishuOpenApiClient(options) {
3036
- const tokenProvider = createFeishuTenantAccessTokenProvider({
3037
- appId: options.config.feishu.appId,
3038
- appSecret: options.config.feishu.appSecret,
3039
- baseUrl: options.config.feishu.baseUrl,
3040
- request: (request) => options.request(request).pipe(Effect.map((response) => response))
3041
- });
3042
- return createFeishuOpenApiClient({
3043
- getTenantAccessToken: () => tokenProvider.getTenantAccessToken(),
3044
- request: (request) => options.request(request).pipe(Effect.map((response) => response))
3045
- });
3046
- }
3047
- //#endregion
3048
3079
  //#region src/infrastructure/feishu/feishu-cardkit-target-preparation.ts
3049
3080
  const DEFAULT_ELEMENT_ID = "rivus_agent_answer";
3050
3081
  function createFeishuCardTargetPreparation(options) {
@@ -3114,6 +3145,7 @@ function isRecord$2(value) {
3114
3145
  //#endregion
3115
3146
  //#region src/composition/rivus-daemon-bootstrap.ts
3116
3147
  const DEFAULT_WORKER_INTERVAL_MS$1 = 250;
3148
+ const DEFAULT_RUN_TIMEOUT_MS = 900 * 1e3;
3117
3149
  function restoreConfiguredRivusDaemonBootstrap(options) {
3118
3150
  return options.eventLog.readAll().pipe(Effect.map((events) => createConfiguredRivusDaemonBootstrap({
3119
3151
  ...options,
@@ -3139,14 +3171,17 @@ function createConfiguredRivusDaemonBootstrap(options) {
3139
3171
  const periodicFlush = createPeriodicFlush(options, publisher);
3140
3172
  const runtime = createFeishuAgentRuntime({
3141
3173
  agentId: options.config.agentId,
3174
+ ...options.botOpenId ? { botOpenId: options.botOpenId } : {},
3142
3175
  clock: options.clock,
3143
3176
  eventSinks: [options.eventLog],
3177
+ ...options.inboxRepository ? { inboxRepository: options.inboxRepository } : {},
3144
3178
  ...options.initialEvents ? { initialEvents: options.initialEvents } : {},
3145
3179
  ...options.initialRunStates ? { initialRunStates: options.initialRunStates } : {},
3146
3180
  loop: options.loop,
3147
3181
  periodicFlush,
3148
3182
  prepareRun,
3149
3183
  publish: (action) => publisher.publish(action),
3184
+ runTimeoutMs: options.runTimeoutMs ?? DEFAULT_RUN_TIMEOUT_MS,
3150
3185
  runIds: options.runIds
3151
3186
  });
3152
3187
  const websocketTransport = createFeishuWebSocketDaemon({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rivus/agent",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
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",