@rivus/agent 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import { a as createAgentLoopSkillExecutionStart, c as createAgentLoopToolExecutionEnd, d as createAsyncIterableAgentLoop, f as createEventAgentLoop, i as createAgentLoopSkillExecutionEnd, l as createAgentLoopToolExecutionStart, m as createTextAgentLoopFromCallback, n as createAgentLoopModelExecutionEnd, o as createAgentLoopTextDelta, p as createTextAgentLoop, r as createAgentLoopModelExecutionStart, s as createAgentLoopThinkingDelta, t as createAgentLoopFromCallback, u as createAgentLoopToolExecutionUpdate } from "./agent-loop.js";
2
2
  import { a as createMemoryNamespace, c as InvalidRivusPlugin, d as requiresToolApproval, i as RIVUS_MEMORY_TOOL_VERSION, l as RIVUS_PLUGIN_API_VERSION, n as RIVUS_MEMORY_TOOL_ID, o as createRivusMemoryToolContract, r as RIVUS_MEMORY_TOOL_PLUGIN_ID, s as restrictMemoryScopesForAudience, t as MEMORY_SCOPES, u as RivusToolInputRejected } from "./agent-memory.js";
3
- import { n as resolveRivusAgentDefinition, t as createRivusPluginCatalog } from "./rivus-plugin-registry.js";
4
- import { A as CardPresentationTransitionDenied, B as FeishuEndpointCredentialError, C as loadRivusDaemonConfig, D as loadRivusDeploymentManifest, E as RivusDeploymentManifestError, F as completeCardPresentationHandoff, G as validateRivusDeploymentManifest, I as createCardPresentationChain, L as failCardPresentationHandoff, M as activeCardPresentation, N as beginCardPresentationHandoff, O as DEFAULT_CARD_STREAM_LEASE_MS, P as compensateCardPresentationHandoff, R as isCardPresentationHandoffDue, S as RivusDaemonConfigError, U as RivusPluginLoadError, V as resolveFeishuEndpointCredentials, W as loadRivusDeployment, _ as createStableId, a as InvalidRivusProjectSpace, b as createRivusEnvFromOpenClawConfig, c as RivusDeploymentReadinessError, d as createRivusAgentHost, f as AgentInstanceBusy, g as createAgentInstanceRegistry, h as AgentInstanceConflict, i as resolveRivusProjectSpace, j as acceptsCardPresentationProgress, k as createFeishuCardRollover, l as createRivusDeploymentDaemon, m as createAgentRuntimePool, n as createRivusDeploymentCliProcess, o as RivusDeploymentAutomationReadinessError, p as AgentRuntimeDisposed, r as createConfiguredRivusDeploymentDaemon, s as RivusDeploymentDaemonLifecycleError, t as runRivusDaemonCli, u as InvalidRivusEndpointBinding, v as createRivusDaemonShutdownController, w as loadNodeRivusPluginModule, x as formatRivusEnvFile, y as OpenClawEnvImportError, z as markCardPresentationTerminal } from "./rivus-daemon-cli.js";
3
+ import { a as BACKGROUND_SESSION_START_TOOL_ID, c as BACKGROUND_SESSION_TOOL_VERSION, d as createBackgroundSessionStepSourceMessageId, f as createBackgroundSessionToolContracts, h as narrowBackgroundSessionDefinition, i as BACKGROUND_SESSION_SESSION_KEY_PREFIX, l as backgroundSessionToolIds, m as isBackgroundSessionToolId, n as resolveRivusAgentDefinition, o as BACKGROUND_SESSION_TOOL_IDS, p as extendBackgroundSessionDefinition, s as BACKGROUND_SESSION_TOOL_PLUGIN_ID, t as createRivusPluginCatalog, u as createBackgroundSessionKey } from "./rivus-plugin-registry.js";
4
+ import { $ as loadRivusDeployment, A as DEFAULT_BACKGROUND_SESSION_LIFETIME_MS, B as acceptsCardPresentationProgress, C as loadRivusDaemonConfig, D as loadRivusDeploymentManifest, E as RivusDeploymentManifestError, F as DEFAULT_BACKGROUND_SESSION_SUPERVISOR_INTERVAL_MS, G as createCardPresentationChain, H as beginCardPresentationHandoff, I as resolveBackgroundSessionSupervisorIntervalMs, J as markCardPresentationTerminal, K as failCardPresentationHandoff, L as DEFAULT_CARD_STREAM_LEASE_MS, M as DEFAULT_BACKGROUND_SESSION_MAX_CONSECUTIVE_FAILURES, N as DEFAULT_BACKGROUND_SESSION_RETRY_BACKOFF_MS, O as DEFAULT_BACKGROUND_SESSION_LEASE_MS, P as DEFAULT_BACKGROUND_SESSION_STEP_TIMEOUT_MS, Q as RivusPluginLoadError, R as createFeishuCardRollover, S as RivusDaemonConfigError, U as compensateCardPresentationHandoff, V as activeCardPresentation, W as completeCardPresentationHandoff, X as resolveFeishuEndpointCredentials, Y as FeishuEndpointCredentialError, _ as createStableId, a as InvalidRivusProjectSpace, b as createRivusEnvFromOpenClawConfig, c as RivusDeploymentReadinessError, d as createRivusAgentHost, et as validateRivusDeploymentManifest, f as AgentInstanceBusy, g as createAgentInstanceRegistry, h as AgentInstanceConflict, i as resolveRivusProjectSpace, j as DEFAULT_BACKGROUND_SESSION_MAX_CONCURRENT_SESSIONS, k as DEFAULT_BACKGROUND_SESSION_LEASE_RENEWAL_INTERVAL_MS, l as createRivusDeploymentDaemon, m as createAgentRuntimePool, n as createRivusDeploymentCliProcess, o as RivusDeploymentAutomationReadinessError, p as AgentRuntimeDisposed, q as isCardPresentationHandoffDue, r as createConfiguredRivusDeploymentDaemon, s as RivusDeploymentDaemonLifecycleError, t as runRivusDaemonCli, u as InvalidRivusEndpointBinding, v as createRivusDaemonShutdownController, w as loadNodeRivusPluginModule, x as formatRivusEnvFile, y as OpenClawEnvImportError, z as CardPresentationTransitionDenied } from "./rivus-daemon-cli.js";
5
5
  import { a as InvalidInvocationAuthority, i as normalizeStableJson, n as InvalidToolInput, o as createInvocationAuthority, r as createToolInputDigest, s as resolveInvocationAuthority, t as InvalidStableJson } from "./tool-input-digest.js";
6
6
  import { n as assertRivusPluginConforms, r as createFakeRivusPlugin, t as RivusPluginConformanceError } from "./rivus-plugin-testkit.js";
7
7
  import { Cause, Deferred, Effect, Either, Exit, Fiber, Option, Stream } from "effect";
8
8
  import { createHash, randomUUID } from "node:crypto";
9
9
  import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
10
- import { appendFile, lstat, mkdir, readFile, readdir, realpath, rename, stat, unlink, writeFile } from "node:fs/promises";
10
+ import { appendFile, lstat, mkdir, readFile, readdir, realpath, rename, stat, truncate, unlink, writeFile } from "node:fs/promises";
11
11
  import { isDeepStrictEqual } from "node:util";
12
12
  import { createServer } from "node:http";
13
13
  import { Buffer as Buffer$1 } from "node:buffer";
@@ -2866,8 +2866,8 @@ function resolveRunPresentation(input) {
2866
2866
  title: input.generation ? "正在处理(续)" : "正在处理"
2867
2867
  };
2868
2868
  case "handoff": return {
2869
- content: "任务仍在后台处理,进度将继续显示在下一条消息。",
2870
- note: "本卡片的流式窗口已交接",
2869
+ content: input.text || "任务仍在后台处理,进度将继续显示在下一条消息。",
2870
+ note: "本卡片已停止更新;后续进度见下一条消息",
2871
2871
  template: "blue",
2872
2872
  title: "已转到新消息"
2873
2873
  };
@@ -2883,14 +2883,35 @@ function resolveRunPresentation(input) {
2883
2883
  template: "red",
2884
2884
  title: "处理失败"
2885
2885
  };
2886
- case "cancelled": return {
2887
- content: "已停止生成。",
2888
- note: "本次运行已取消",
2889
- template: "grey",
2890
- title: "已取消"
2891
- };
2886
+ case "cancelled": return resolveCancellationPresentation(input.reason, input.text);
2892
2887
  }
2893
2888
  }
2889
+ function resolveCancellationPresentation(reason, text) {
2890
+ const timeoutMs = readRunTimeoutMs(reason);
2891
+ if (timeoutMs !== void 0) return {
2892
+ content: text || `任务执行超过 ${formatDuration(timeoutMs)},已停止。本次没有形成最终结论。`,
2893
+ note: `达到 ${formatDuration(timeoutMs)}运行时限;以上是超时前进度,不是最终结论`,
2894
+ template: "orange",
2895
+ title: "执行超时"
2896
+ };
2897
+ return {
2898
+ content: text || "已停止生成。",
2899
+ note: "本次运行已取消",
2900
+ template: "grey",
2901
+ title: "已取消"
2902
+ };
2903
+ }
2904
+ function readRunTimeoutMs(reason) {
2905
+ const match = /^run timed out after (\d+)ms$/.exec(reason ?? "");
2906
+ if (!match) return void 0;
2907
+ const milliseconds = Number(match[1]);
2908
+ return Number.isSafeInteger(milliseconds) && milliseconds > 0 ? milliseconds : void 0;
2909
+ }
2910
+ function formatDuration(milliseconds) {
2911
+ if (milliseconds % 36e5 === 0) return `${milliseconds / 36e5} 小时`;
2912
+ if (milliseconds % 6e4 === 0) return `${milliseconds / 6e4} 分钟`;
2913
+ return `${Math.ceil(milliseconds / 1e3)} 秒`;
2914
+ }
2894
2915
  //#endregion
2895
2916
  //#region src/infrastructure/feishu/feishu-tenant-token-provider.ts
2896
2917
  var FeishuTenantAccessTokenError = class {
@@ -3004,10 +3025,20 @@ function isStreamingClosed(error) {
3004
3025
  return error instanceof FeishuOpenApiError && (error.code === 200850 || error.code === 300309);
3005
3026
  }
3006
3027
  function updateWholeCard(baseUrl, agentName, request, status, call) {
3028
+ const requestText = "text" in request ? request.text : void 0;
3007
3029
  const card = status === "completed" ? createFeishuAgentRunCard({
3008
3030
  agentName,
3009
3031
  status,
3010
- text: "text" in request ? request.text : ""
3032
+ text: requestText ?? ""
3033
+ }) : status === "cancelled" ? createFeishuAgentRunCard({
3034
+ agentName,
3035
+ ..."reason" in request && request.reason ? { reason: request.reason } : {},
3036
+ status,
3037
+ ...requestText === void 0 ? {} : { text: requestText }
3038
+ }) : status === "handoff" ? createFeishuAgentRunCard({
3039
+ agentName,
3040
+ status,
3041
+ ...requestText === void 0 ? {} : { text: requestText }
3011
3042
  }) : createFeishuAgentRunCard({
3012
3043
  agentName,
3013
3044
  status
@@ -3040,7 +3071,8 @@ function createFeishuCardKitPublisher(options) {
3040
3071
  });
3041
3072
  case "handoff": return yield* options.client.handoff({
3042
3073
  cardId: target.cardId,
3043
- sequence
3074
+ sequence,
3075
+ ...action.text === void 0 ? {} : { text: action.text }
3044
3076
  });
3045
3077
  case "finish":
3046
3078
  yield* options.client.finish({
@@ -3060,7 +3092,8 @@ function createFeishuCardKitPublisher(options) {
3060
3092
  yield* options.client.cancel({
3061
3093
  cardId: target.cardId,
3062
3094
  ...action.reason ? { reason: action.reason } : {},
3063
- sequence
3095
+ sequence,
3096
+ ...action.text === void 0 ? {} : { text: action.text }
3064
3097
  });
3065
3098
  return yield* ledger.markTerminal(action.runId);
3066
3099
  }
@@ -3168,7 +3201,7 @@ function createFeishuCardKitOpenApiTargetCreator(options) {
3168
3201
  runId: run.runId,
3169
3202
  sessionKey: run.sessionKey,
3170
3203
  status: "running",
3171
- text: initialContent
3204
+ text: createOptions?.initialContent ?? initialContent
3172
3205
  })),
3173
3206
  type: "card_json"
3174
3207
  },
@@ -3225,7 +3258,10 @@ function createConfiguredFeishuCardRolloverRuntime(options) {
3225
3258
  });
3226
3259
  const cardRollover = createFeishuCardRollover({
3227
3260
  clock: options.clock,
3228
- createSuccessor: ({ generation, run }) => creator.createTarget(run, { generation }),
3261
+ createSuccessor: ({ generation, run, text }) => creator.createTarget(run, {
3262
+ generation,
3263
+ ...text === void 0 ? {} : { initialContent: text }
3264
+ }),
3229
3265
  leaseMs,
3230
3266
  ...options.observe ? { observe: options.observe } : {},
3231
3267
  publisher,
@@ -3407,36 +3443,36 @@ function parseAgentDomainEvent(value, lineNumber) {
3407
3443
  switch (value.type) {
3408
3444
  case "agent_run_accepted": return {
3409
3445
  occurredAt,
3410
- prompt: readString(value, "prompt", lineNumber),
3411
- runId: readString(value, "runId", lineNumber),
3412
- sessionKey: readString(value, "sessionKey", lineNumber),
3446
+ prompt: readString$1(value, "prompt", lineNumber),
3447
+ runId: readString$1(value, "runId", lineNumber),
3448
+ sessionKey: readString$1(value, "sessionKey", lineNumber),
3413
3449
  type: value.type
3414
3450
  };
3415
3451
  case "agent_turn_started": return {
3416
3452
  occurredAt,
3417
- runId: readString(value, "runId", lineNumber),
3418
- sessionKey: readString(value, "sessionKey", lineNumber),
3453
+ runId: readString$1(value, "runId", lineNumber),
3454
+ sessionKey: readString$1(value, "sessionKey", lineNumber),
3419
3455
  type: value.type
3420
3456
  };
3421
3457
  case "assistant_text_delta": return {
3422
- delta: readString(value, "delta", lineNumber),
3458
+ delta: readString$1(value, "delta", lineNumber),
3423
3459
  occurredAt,
3424
- runId: readString(value, "runId", lineNumber),
3460
+ runId: readString$1(value, "runId", lineNumber),
3425
3461
  type: value.type
3426
3462
  };
3427
3463
  case "assistant_thinking_delta": return {
3428
- delta: readString(value, "delta", lineNumber),
3464
+ delta: readString$1(value, "delta", lineNumber),
3429
3465
  occurredAt,
3430
- runId: readString(value, "runId", lineNumber),
3466
+ runId: readString$1(value, "runId", lineNumber),
3431
3467
  type: value.type
3432
3468
  };
3433
3469
  case "agent_model_execution_started": return {
3434
- api: readString(value, "api", lineNumber),
3435
- model: readString(value, "model", lineNumber),
3436
- modelCallId: readString(value, "modelCallId", lineNumber),
3470
+ api: readString$1(value, "api", lineNumber),
3471
+ model: readString$1(value, "model", lineNumber),
3472
+ modelCallId: readString$1(value, "modelCallId", lineNumber),
3437
3473
  occurredAt,
3438
- provider: readString(value, "provider", lineNumber),
3439
- runId: readString(value, "runId", lineNumber),
3474
+ provider: readString$1(value, "provider", lineNumber),
3475
+ runId: readString$1(value, "runId", lineNumber),
3440
3476
  type: value.type
3441
3477
  };
3442
3478
  case "agent_model_execution_ended": {
@@ -3445,15 +3481,15 @@ function parseAgentDomainEvent(value, lineNumber) {
3445
3481
  const responseModel = readOptionalString(value, "responseModel", lineNumber);
3446
3482
  const reasoning = readOptionalNumber(usage, "reasoning", lineNumber);
3447
3483
  return {
3448
- api: readString(value, "api", lineNumber),
3484
+ api: readString$1(value, "api", lineNumber),
3449
3485
  ...errorMessage ? { errorMessage } : {},
3450
- model: readString(value, "model", lineNumber),
3451
- modelCallId: readString(value, "modelCallId", lineNumber),
3486
+ model: readString$1(value, "model", lineNumber),
3487
+ modelCallId: readString$1(value, "modelCallId", lineNumber),
3452
3488
  occurredAt,
3453
- provider: readString(value, "provider", lineNumber),
3489
+ provider: readString$1(value, "provider", lineNumber),
3454
3490
  ...responseModel ? { responseModel } : {},
3455
- runId: readString(value, "runId", lineNumber),
3456
- stopReason: readString(value, "stopReason", lineNumber),
3491
+ runId: readString$1(value, "runId", lineNumber),
3492
+ stopReason: readString$1(value, "stopReason", lineNumber),
3457
3493
  type: value.type,
3458
3494
  usage: {
3459
3495
  cacheRead: readNumber(usage, "cacheRead", lineNumber),
@@ -3483,10 +3519,10 @@ function parseAgentDomainEvent(value, lineNumber) {
3483
3519
  } : {
3484
3520
  ...common,
3485
3521
  contentLength: readNumber(value, "contentLength", lineNumber),
3486
- digest: readString(value, "digest", lineNumber),
3522
+ digest: readString$1(value, "digest", lineNumber),
3487
3523
  isError: false,
3488
- title: readString(value, "title", lineNumber),
3489
- version: readString(value, "version", lineNumber)
3524
+ title: readString$1(value, "title", lineNumber),
3525
+ version: readString$1(value, "version", lineNumber)
3490
3526
  };
3491
3527
  }
3492
3528
  case "agent_tool_execution_started": return {
@@ -3510,21 +3546,21 @@ function parseAgentDomainEvent(value, lineNumber) {
3510
3546
  type: value.type
3511
3547
  };
3512
3548
  case "agent_turn_completed": return {
3513
- finalText: readString(value, "finalText", lineNumber),
3549
+ finalText: readString$1(value, "finalText", lineNumber),
3514
3550
  occurredAt,
3515
- runId: readString(value, "runId", lineNumber),
3551
+ runId: readString$1(value, "runId", lineNumber),
3516
3552
  type: value.type
3517
3553
  };
3518
3554
  case "agent_run_completed": return {
3519
- finalText: readString(value, "finalText", lineNumber),
3555
+ finalText: readString$1(value, "finalText", lineNumber),
3520
3556
  occurredAt,
3521
- runId: readString(value, "runId", lineNumber),
3557
+ runId: readString$1(value, "runId", lineNumber),
3522
3558
  type: value.type
3523
3559
  };
3524
3560
  case "agent_run_failed": return {
3525
- errorMessage: readString(value, "errorMessage", lineNumber),
3561
+ errorMessage: readString$1(value, "errorMessage", lineNumber),
3526
3562
  occurredAt,
3527
- runId: readString(value, "runId", lineNumber),
3563
+ runId: readString$1(value, "runId", lineNumber),
3528
3564
  type: value.type
3529
3565
  };
3530
3566
  case "agent_run_cancelled": {
@@ -3532,7 +3568,7 @@ function parseAgentDomainEvent(value, lineNumber) {
3532
3568
  return {
3533
3569
  occurredAt,
3534
3570
  ...reason ? { reason } : {},
3535
- runId: readString(value, "runId", lineNumber),
3571
+ runId: readString$1(value, "runId", lineNumber),
3536
3572
  type: value.type
3537
3573
  };
3538
3574
  }
@@ -3541,19 +3577,19 @@ function parseAgentDomainEvent(value, lineNumber) {
3541
3577
  }
3542
3578
  function readSkillExecutionIdentity(value, lineNumber) {
3543
3579
  return {
3544
- runId: readString(value, "runId", lineNumber),
3545
- skillCallId: readString(value, "skillCallId", lineNumber),
3546
- skillId: readString(value, "skillId", lineNumber)
3580
+ runId: readString$1(value, "runId", lineNumber),
3581
+ skillCallId: readString$1(value, "skillCallId", lineNumber),
3582
+ skillId: readString$1(value, "skillId", lineNumber)
3547
3583
  };
3548
3584
  }
3549
3585
  function readToolExecutionIdentity(value, lineNumber) {
3550
3586
  return {
3551
- runId: readString(value, "runId", lineNumber),
3552
- toolCallId: readString(value, "toolCallId", lineNumber),
3553
- toolName: readString(value, "toolName", lineNumber)
3587
+ runId: readString$1(value, "runId", lineNumber),
3588
+ toolCallId: readString$1(value, "toolCallId", lineNumber),
3589
+ toolName: readString$1(value, "toolName", lineNumber)
3554
3590
  };
3555
3591
  }
3556
- function readString(value, field, lineNumber) {
3592
+ function readString$1(value, field, lineNumber) {
3557
3593
  const fieldValue = value[field];
3558
3594
  if (typeof fieldValue !== "string") throw new Error(`Invalid agent event field '${field}' at line ${lineNumber}.`);
3559
3595
  return fieldValue;
@@ -4724,7 +4760,7 @@ function unwrapResult(value) {
4724
4760
  }
4725
4761
  function presentResult(toolName, value) {
4726
4762
  if (isTruncated(value)) return "结果过长,已省略。";
4727
- if (!isRecord(value)) return truncate(presentValue(value) ?? "工具执行完成。", 2e3);
4763
+ if (!isRecord(value)) return truncate$1(presentValue(value) ?? "工具执行完成。", 2e3);
4728
4764
  const tool = resolveTool(toolName);
4729
4765
  const orderedKeys = tool.fields?.map(([key]) => key) ?? [];
4730
4766
  const lines = Object.entries(value).sort(([left], [right]) => {
@@ -4736,7 +4772,7 @@ function presentResult(toolName, value) {
4736
4772
  const presented = presentValue(item, field?.[2]);
4737
4773
  return presented ? [`${clean(field?.[1] ?? humanizeKey(key), 80)}:${presented}`] : [];
4738
4774
  }).slice(0, 12);
4739
- return truncate(lines.length > 0 ? lines.join("\n") : "工具执行完成。", 2e3);
4775
+ return truncate$1(lines.length > 0 ? lines.join("\n") : "工具执行完成。", 2e3);
4740
4776
  }
4741
4777
  function presentValue(value, unit) {
4742
4778
  if (value === REDACTED || value === TRUNCATED || value === void 0) return void 0;
@@ -4760,9 +4796,9 @@ function humanizeKey(key) {
4760
4796
  return `${text.slice(0, 1).toUpperCase()}${text.slice(1)}` || "结果";
4761
4797
  }
4762
4798
  function clean(value, maxLength) {
4763
- return truncate(value.replace(/[\p{Cc}\p{Cf}]+/gu, " ").replace(/\s+/gu, " ").trim(), maxLength);
4799
+ return truncate$1(value.replace(/[\p{Cc}\p{Cf}]+/gu, " ").replace(/\s+/gu, " ").trim(), maxLength);
4764
4800
  }
4765
- function truncate(value, maxLength) {
4801
+ function truncate$1(value, maxLength) {
4766
4802
  return value.length <= maxLength ? value : `${value.slice(0, maxLength - 1)}…`;
4767
4803
  }
4768
4804
  function isRecord(value) {
@@ -5310,7 +5346,14 @@ function createToolBroker(options) {
5310
5346
  runId: authority.runId,
5311
5347
  sessionKey: authority.sessionKey,
5312
5348
  toolId: tool.id,
5313
- toolVersion: tool.version
5349
+ toolVersion: tool.version,
5350
+ ...authority.endpointId ? { origin: Object.freeze({
5351
+ ...authority.allowedActorOpenIds ? { allowedActorOpenIds: authority.allowedActorOpenIds } : { allowedActorOpenIds: Object.freeze([]) },
5352
+ endpointId: authority.endpointId,
5353
+ tenantKey: authority.tenantKey,
5354
+ ...authority.conversationId ? { conversationId: authority.conversationId } : {}
5355
+ }) } : {},
5356
+ ...authority.sourceMessageId ? { sourceMessageId: authority.sourceMessageId } : {}
5314
5357
  });
5315
5358
  const replayCompleted = (result) => "replayCompleted" in tool && tool.replayCompleted ? tool.replayCompleted(request.input, result, context) : result;
5316
5359
  if (request.operationId) {
@@ -5845,6 +5888,1483 @@ function isVisibleMemory(record) {
5845
5888
  return record.scope !== "shared-user-profile" && record.scope !== "project" || record.state === "confirmed";
5846
5889
  }
5847
5890
  //#endregion
5891
+ //#region src/domain/background-agent-session.ts
5892
+ const BACKGROUND_SESSION_TERMINAL_PHASES = /* @__PURE__ */ new Set([
5893
+ "stopped",
5894
+ "completed",
5895
+ "failed",
5896
+ "reconciliation-required"
5897
+ ]);
5898
+ function isBackgroundSessionTerminalPhase(phase) {
5899
+ return BACKGROUND_SESSION_TERMINAL_PHASES.has(phase);
5900
+ }
5901
+ var BackgroundSessionTransitionDenied = class extends Error {
5902
+ sessionId;
5903
+ name = "BackgroundSessionTransitionDenied";
5904
+ constructor(sessionId, message) {
5905
+ super(message);
5906
+ this.sessionId = sessionId;
5907
+ }
5908
+ };
5909
+ function createBackgroundSession(input) {
5910
+ if (!input.sessionId.trim()) throw new BackgroundSessionTransitionDenied(input.sessionId, "background session id must not be empty");
5911
+ if (!input.authority.sessionKey.trim()) throw new BackgroundSessionTransitionDenied(input.sessionId, "background session key must not be empty");
5912
+ if (!input.authority.agentId.trim()) throw new BackgroundSessionTransitionDenied(input.sessionId, "background session authority requires an agent id");
5913
+ if (!input.sourceMessageId.trim()) throw new BackgroundSessionTransitionDenied(input.sessionId, "background session requires a trusted source message id");
5914
+ return Object.freeze({
5915
+ authority: Object.freeze({
5916
+ ...input.authority,
5917
+ memoryScopes: Object.freeze([...input.authority.memoryScopes])
5918
+ }),
5919
+ consecutiveFailures: 0,
5920
+ createdAt: input.now,
5921
+ displayName: input.displayName,
5922
+ origin: Object.freeze({
5923
+ ...input.origin,
5924
+ allowedActorOpenIds: Object.freeze([...input.origin.allowedActorOpenIds])
5925
+ }),
5926
+ parentRunId: input.parentRunId,
5927
+ pendingInput: Object.freeze([]),
5928
+ phase: "queued",
5929
+ prompt: input.prompt,
5930
+ revision: 1,
5931
+ sessionId: input.sessionId,
5932
+ sourceMessageId: input.sourceMessageId,
5933
+ stepCount: 0,
5934
+ updatedAt: input.now,
5935
+ wakeCount: 0
5936
+ });
5937
+ }
5938
+ function claimBackgroundSession(state, input) {
5939
+ if (isBackgroundSessionTerminalPhase(state.phase)) throw new BackgroundSessionTransitionDenied(state.sessionId, `terminal ${state.phase} session cannot run again`);
5940
+ if (state.phase !== "queued" && state.phase !== "waiting") throw new BackgroundSessionTransitionDenied(state.sessionId, `background session cannot start a step while ${state.phase}`);
5941
+ if (state.wakeAt !== void 0 && state.wakeAt > input.now) throw new BackgroundSessionTransitionDenied(state.sessionId, "background session is not due");
5942
+ return next(state, {
5943
+ currentStepRunId: input.stepRunId,
5944
+ lastWakeText: input.wakeText,
5945
+ lease: input.lease,
5946
+ pendingInput: Object.freeze([]),
5947
+ phase: "running",
5948
+ stepCount: state.stepCount + 1,
5949
+ updatedAt: input.now,
5950
+ wakeAt: void 0,
5951
+ wakeCount: state.wakeCount + 1
5952
+ });
5953
+ }
5954
+ function renewBackgroundSessionLease(state, lease) {
5955
+ if (state.lease === void 0) throw new BackgroundSessionTransitionDenied(state.sessionId, "cannot renew a lease that is not held");
5956
+ if (state.lease.owner !== lease.owner || state.lease.epoch !== lease.epoch) throw new BackgroundSessionTransitionDenied(state.sessionId, "cannot renew a lease from another worker");
5957
+ return next(state, { lease });
5958
+ }
5959
+ function releaseBackgroundSessionLease(state) {
5960
+ if (state.lease === void 0) throw new BackgroundSessionTransitionDenied(state.sessionId, "no lease to release");
5961
+ return next(state, { lease: void 0 });
5962
+ }
5963
+ function suspendBackgroundSession(state, input) {
5964
+ if (state.phase !== "running") throw new BackgroundSessionTransitionDenied(state.sessionId, `background session cannot wait while ${state.phase}`);
5965
+ if (input.wakeAt !== void 0 && input.wakeAt <= input.now) throw new BackgroundSessionTransitionDenied(state.sessionId, "background session wake time must be in the future");
5966
+ return next(state, {
5967
+ currentStepRunId: void 0,
5968
+ phase: input.wakeAt === void 0 ? "input-required" : "waiting",
5969
+ updatedAt: input.now,
5970
+ ...input.wakeAt === void 0 ? {} : { wakeAt: input.wakeAt }
5971
+ });
5972
+ }
5973
+ function completeBackgroundSessionStep(state, input) {
5974
+ if (state.phase !== "running") throw new BackgroundSessionTransitionDenied(state.sessionId, `background session cannot complete while ${state.phase}`);
5975
+ return next(state, {
5976
+ consecutiveFailures: 0,
5977
+ currentStepRunId: void 0,
5978
+ lease: void 0,
5979
+ phase: "completed",
5980
+ result: {
5981
+ completedAt: input.now,
5982
+ stepRunId: input.stepRunId,
5983
+ text: input.text
5984
+ },
5985
+ updatedAt: input.now
5986
+ });
5987
+ }
5988
+ function failBackgroundSessionStep(state, input) {
5989
+ if (state.phase !== "running") throw new BackgroundSessionTransitionDenied(state.sessionId, `background session cannot fail while ${state.phase}`);
5990
+ if (input.retryable) {
5991
+ if (input.wakeAt === void 0 || input.wakeAt <= input.now) throw new BackgroundSessionTransitionDenied(state.sessionId, "retryable failure requires a future wake time");
5992
+ return next(state, {
5993
+ consecutiveFailures: state.consecutiveFailures + 1,
5994
+ currentStepRunId: void 0,
5995
+ errorMessage: input.errorMessage,
5996
+ lease: void 0,
5997
+ phase: "queued",
5998
+ updatedAt: input.now,
5999
+ wakeAt: input.wakeAt
6000
+ });
6001
+ }
6002
+ return next(state, {
6003
+ consecutiveFailures: state.consecutiveFailures + 1,
6004
+ currentStepRunId: void 0,
6005
+ errorMessage: input.errorMessage,
6006
+ lease: void 0,
6007
+ phase: "failed",
6008
+ updatedAt: input.now
6009
+ });
6010
+ }
6011
+ function requestBackgroundSessionStop(state, input) {
6012
+ if (isBackgroundSessionTerminalPhase(state.phase)) throw new BackgroundSessionTransitionDenied(state.sessionId, `terminal ${state.phase} session cannot stop`);
6013
+ if (state.phase === "stopping") throw new BackgroundSessionTransitionDenied(state.sessionId, "background session is already stopping");
6014
+ if (state.phase === "running") return next(state, {
6015
+ cancellation: {
6016
+ reason: input.reason,
6017
+ requestedAt: input.now
6018
+ },
6019
+ phase: "stopping",
6020
+ updatedAt: input.now
6021
+ });
6022
+ return next(state, {
6023
+ cancellation: {
6024
+ reason: input.reason,
6025
+ requestedAt: input.now
6026
+ },
6027
+ currentStepRunId: void 0,
6028
+ lease: void 0,
6029
+ phase: "stopped",
6030
+ updatedAt: input.now,
6031
+ wakeAt: void 0
6032
+ });
6033
+ }
6034
+ function completeBackgroundSessionStop(state, input) {
6035
+ if (state.phase !== "stopping") throw new BackgroundSessionTransitionDenied(state.sessionId, `background session cannot finish stopping while ${state.phase}`);
6036
+ return next(state, {
6037
+ currentStepRunId: void 0,
6038
+ lease: void 0,
6039
+ phase: "stopped",
6040
+ updatedAt: input.now,
6041
+ wakeAt: void 0
6042
+ });
6043
+ }
6044
+ function parkBackgroundSessionForReconciliation(state, input) {
6045
+ if (isBackgroundSessionTerminalPhase(state.phase) && state.phase !== "reconciliation-required") throw new BackgroundSessionTransitionDenied(state.sessionId, `terminal ${state.phase} session cannot be reconciled`);
6046
+ return next(state, {
6047
+ currentStepRunId: void 0,
6048
+ errorMessage: input.reason,
6049
+ lease: void 0,
6050
+ phase: "reconciliation-required",
6051
+ reconciliationNote: input.reason,
6052
+ updatedAt: input.now,
6053
+ wakeAt: void 0
6054
+ });
6055
+ }
6056
+ function resolveBackgroundSessionReconciliation(state, input) {
6057
+ if (state.phase !== "reconciliation-required") throw new BackgroundSessionTransitionDenied(state.sessionId, `background session cannot resolve while ${state.phase}`);
6058
+ if (input.outcome === "continue") return next(state, {
6059
+ errorMessage: void 0,
6060
+ phase: "queued",
6061
+ reconciliationNote: void 0,
6062
+ updatedAt: input.now,
6063
+ wakeAt: input.now
6064
+ });
6065
+ return next(state, {
6066
+ errorMessage: void 0,
6067
+ phase: "stopped",
6068
+ reconciliationNote: void 0,
6069
+ updatedAt: input.now,
6070
+ wakeAt: void 0
6071
+ });
6072
+ }
6073
+ function appendBackgroundSessionInput(state, input) {
6074
+ if (isBackgroundSessionTerminalPhase(state.phase)) throw new BackgroundSessionTransitionDenied(state.sessionId, `terminal ${state.phase} session cannot accept input`);
6075
+ return next(state, {
6076
+ pendingInput: Object.freeze([...state.pendingInput, input.message]),
6077
+ updatedAt: input.now,
6078
+ ...state.phase === "waiting" || state.phase === "input-required" ? {
6079
+ phase: "queued",
6080
+ wakeAt: input.now
6081
+ } : {}
6082
+ });
6083
+ }
6084
+ function requeueInterruptedBackgroundSessionStep(state, input) {
6085
+ if (state.phase !== "running") throw new BackgroundSessionTransitionDenied(state.sessionId, "interrupted step recovery requires a running session");
6086
+ return next(state, {
6087
+ currentStepRunId: void 0,
6088
+ lastWakeText: input.wakeText,
6089
+ lease: void 0,
6090
+ phase: "queued",
6091
+ updatedAt: input.now,
6092
+ wakeAt: input.now
6093
+ });
6094
+ }
6095
+ function isBackgroundSessionLeaseExpired(state, now) {
6096
+ return state.lease !== void 0 && state.lease.expiresAt <= now;
6097
+ }
6098
+ function isBackgroundSessionDue(state, now) {
6099
+ if (state.phase !== "queued" && state.phase !== "waiting") return false;
6100
+ return state.wakeAt === void 0 || state.wakeAt <= now;
6101
+ }
6102
+ function next(state, changes) {
6103
+ const merged = {
6104
+ ...state,
6105
+ ...changes,
6106
+ revision: state.revision + 1
6107
+ };
6108
+ for (const key of Object.keys(merged)) if (merged[key] === void 0) delete merged[key];
6109
+ return Object.freeze(merged);
6110
+ }
6111
+ function isValidBackgroundSessionTransition(previous, nextState) {
6112
+ if (nextState.sessionId !== previous.sessionId) return "session id must not change";
6113
+ if (nextState.revision !== previous.revision + 1) return "revision must increase by exactly one";
6114
+ if (nextState.authority.agentId !== previous.authority.agentId) return "authority agent must not change";
6115
+ if (nextState.authority.profileRevision !== previous.authority.profileRevision) return "authority profile revision must not change";
6116
+ if (nextState.authority.toolGrantRevision !== previous.authority.toolGrantRevision) return "authority tool grant revision must not change";
6117
+ if (nextState.authority.sessionKey !== previous.authority.sessionKey) return "authority session key must not change";
6118
+ if (nextState.origin.endpointId !== previous.origin.endpointId) return "origin endpoint must not change";
6119
+ if (nextState.origin.tenantKey !== previous.origin.tenantKey) return "origin tenant must not change";
6120
+ if (nextState.origin.conversationId !== previous.origin.conversationId) return "origin conversation must not change";
6121
+ if (nextState.parentRunId !== previous.parentRunId) return "parent run must not change";
6122
+ if (nextState.sourceMessageId !== previous.sourceMessageId) return "source message must not change";
6123
+ if (nextState.prompt !== previous.prompt) return "prompt must not change";
6124
+ if (nextState.stepCount < previous.stepCount) return "step count must not decrease";
6125
+ if (nextState.wakeCount < previous.wakeCount) return "wake count must not decrease";
6126
+ if (nextState.consecutiveFailures < previous.consecutiveFailures) {
6127
+ if (previous.consecutiveFailures > 0 && nextState.phase === "completed" && nextState.consecutiveFailures === 0) return;
6128
+ return "failure count must not decrease";
6129
+ }
6130
+ if (nextState.createdAt !== previous.createdAt) return "created time must not change";
6131
+ if (previous.phase === "running") {
6132
+ if (nextState.phase === "running") return holdsSameLease(previous, nextState) ? void 0 : "running lease owner or epoch must not change";
6133
+ if (nextState.phase === "waiting" || nextState.phase === "input-required") return holdsSameLease(previous, nextState) ? void 0 : "suspension requires the same lease";
6134
+ if (nextState.phase === "completed" && nextState.result !== void 0 && nextState.lease === void 0) return;
6135
+ if (nextState.phase === "failed" && nextState.errorMessage !== void 0 && nextState.lease === void 0) return;
6136
+ if (nextState.phase === "stopping" && nextState.cancellation !== void 0) return void 0;
6137
+ if (nextState.phase === "reconciliation-required" && nextState.lease === void 0) return void 0;
6138
+ if (nextState.phase === "queued" && nextState.lease === void 0 && nextState.wakeAt !== void 0) return;
6139
+ return `running session cannot transition to ${nextState.phase}`;
6140
+ }
6141
+ if (previous.phase === "stopping") {
6142
+ if (nextState.phase === "stopped" && nextState.lease === void 0 && nextState.cancellation !== void 0) return;
6143
+ if (nextState.phase === "reconciliation-required" && nextState.lease === void 0) return void 0;
6144
+ return `stopping session cannot transition to ${nextState.phase}`;
6145
+ }
6146
+ if (previous.phase === "queued") {
6147
+ if (nextState.phase === "running" && nextState.lease !== void 0) return void 0;
6148
+ if (nextState.phase === "queued" && nextState.lease === void 0 && nextState.wakeAt === previous.wakeAt) return;
6149
+ if (nextState.phase === "stopped" && nextState.lease === void 0) return void 0;
6150
+ if (nextState.phase === "reconciliation-required" && nextState.lease === void 0) return void 0;
6151
+ return `queued session cannot transition to ${nextState.phase}`;
6152
+ }
6153
+ if (previous.phase === "waiting") {
6154
+ if (nextState.phase === "running" && nextState.lease !== void 0) return void 0;
6155
+ if (nextState.phase === "waiting" && nextState.lease === void 0 && nextState.wakeAt === previous.wakeAt) return;
6156
+ if (nextState.phase === "queued" && nextState.wakeAt !== void 0) return void 0;
6157
+ if (nextState.phase === "stopped" && nextState.lease === void 0) return void 0;
6158
+ if (nextState.phase === "reconciliation-required" && nextState.lease === void 0) return void 0;
6159
+ return `waiting session cannot transition to ${nextState.phase}`;
6160
+ }
6161
+ if (previous.phase === "input-required") {
6162
+ if (nextState.phase === "input-required" && nextState.lease === void 0) return void 0;
6163
+ if (nextState.phase === "queued" && nextState.wakeAt !== void 0) return void 0;
6164
+ if (nextState.phase === "stopped" && nextState.lease === void 0) return void 0;
6165
+ if (nextState.phase === "reconciliation-required" && nextState.lease === void 0) return void 0;
6166
+ return `input-required session cannot transition to ${nextState.phase}`;
6167
+ }
6168
+ if (isBackgroundSessionTerminalPhase(previous.phase)) {
6169
+ if (previous.phase === "reconciliation-required") {
6170
+ if (nextState.phase !== "queued" && nextState.phase !== "stopped") return "reconciliation-required session can only continue or stop";
6171
+ return;
6172
+ }
6173
+ return `terminal ${previous.phase} session cannot transition`;
6174
+ }
6175
+ }
6176
+ function holdsSameLease(previous, nextState) {
6177
+ return nextState.lease !== void 0 && previous.lease !== void 0 && nextState.lease.owner === previous.lease.owner && nextState.lease.epoch === previous.lease.epoch;
6178
+ }
6179
+ //#endregion
6180
+ //#region src/application/background-session/background-session-service.ts
6181
+ var BackgroundSessionCallerDenied = class extends Error {
6182
+ name = "BackgroundSessionCallerDenied";
6183
+ constructor(message) {
6184
+ super(message);
6185
+ }
6186
+ };
6187
+ function createBackgroundSessionService(options) {
6188
+ const serial = createSerialExecutor();
6189
+ const start = async (input) => {
6190
+ const now = options.clock.now();
6191
+ const origin = requireOrigin(input.context);
6192
+ const state = createBackgroundSession({
6193
+ authority: input.authority,
6194
+ displayName: input.displayName?.trim() || input.sessionId,
6195
+ now,
6196
+ origin: {
6197
+ allowedActorOpenIds: origin.allowedActorOpenIds,
6198
+ ...origin.conversationId === void 0 ? {} : { conversationId: origin.conversationId },
6199
+ endpointId: origin.endpointId,
6200
+ ...input.context.memory ? { memory: {
6201
+ audience: input.context.memory.audience,
6202
+ ...input.context.memory.conversationId ? { conversationId: input.context.memory.conversationId } : {},
6203
+ ...input.context.memory.projectId ? { projectId: input.context.memory.projectId } : {},
6204
+ subjectId: input.context.memory.subjectId,
6205
+ tenantId: input.context.memory.tenantId
6206
+ } } : {},
6207
+ tenantKey: origin.tenantKey
6208
+ },
6209
+ parentRunId: input.context.runId,
6210
+ prompt: input.prompt,
6211
+ sessionId: input.sessionId,
6212
+ sourceMessageId: requireSourceMessageId(input.context)
6213
+ });
6214
+ return toSummary(await options.repository.create(state));
6215
+ };
6216
+ const wait = async (input) => {
6217
+ const sessionId = sessionIdFromSessionKey(input.context.sessionKey);
6218
+ if (!sessionId) throw new BackgroundSessionCallerDenied("background.wait is only available inside a background session step");
6219
+ if (input.delayMs !== void 0 && input.until !== void 0) throw new BackgroundSessionCallerDenied("background.wait cannot combine delayMs and until");
6220
+ const now = options.clock.now();
6221
+ let wakeAt;
6222
+ if (input.delayMs !== void 0) {
6223
+ if (!Number.isSafeInteger(input.delayMs) || input.delayMs < 1) throw new BackgroundSessionCallerDenied("background.wait delayMs must be a positive integer");
6224
+ wakeAt = new Date(Date.parse(now) + input.delayMs).toISOString();
6225
+ } else if (input.until !== void 0) {
6226
+ const parsed = Date.parse(input.until);
6227
+ if (!Number.isFinite(parsed)) throw new BackgroundSessionCallerDenied("background.wait until must be an ISO 8601 timestamp");
6228
+ wakeAt = new Date(parsed).toISOString();
6229
+ }
6230
+ return toSummary(requireState(await serial.run(async () => {
6231
+ const state = await options.repository.get(sessionId);
6232
+ if (!state) throw new BackgroundSessionCallerDenied(`background session not found: ${sessionId}`);
6233
+ requireMatchingOrigin(state, input.context);
6234
+ const nextState = suspendBackgroundSession(state, {
6235
+ now,
6236
+ ...input.reason === void 0 ? {} : { reason: input.reason },
6237
+ ...wakeAt ? { wakeAt } : {}
6238
+ });
6239
+ return options.repository.update({
6240
+ build: () => nextState,
6241
+ expectedRevision: state.revision,
6242
+ sessionId
6243
+ });
6244
+ }), sessionId));
6245
+ };
6246
+ const list = async (input) => {
6247
+ return (await options.repository.list(input.phase === void 0 ? void 0 : { phase: input.phase })).filter((state) => matchesOrigin(state, input.context)).sort((left, right) => right.createdAt.localeCompare(left.createdAt)).slice(0, input.limit).map(toSummary);
6248
+ };
6249
+ const status = async (input) => {
6250
+ return toDetail(await requireOwnedState(input.sessionId, input.context));
6251
+ };
6252
+ const send = async (input) => {
6253
+ return toSummary(requireState(await serial.run(async () => {
6254
+ const state = await requireOwnedState(input.sessionId, input.context);
6255
+ const nextState = appendBackgroundSessionInput(state, {
6256
+ message: input.message,
6257
+ now: options.clock.now()
6258
+ });
6259
+ return options.repository.update({
6260
+ build: () => nextState,
6261
+ expectedRevision: state.revision,
6262
+ sessionId: input.sessionId
6263
+ });
6264
+ }), input.sessionId));
6265
+ };
6266
+ const stop = async (input) => {
6267
+ return toSummary(requireState(await serial.run(async () => {
6268
+ const state = await requireOwnedState(input.sessionId, input.context);
6269
+ const nextState = requestBackgroundSessionStop(state, {
6270
+ now: options.clock.now(),
6271
+ reason: input.reason ?? "stopped by conversation"
6272
+ });
6273
+ return updateAndEnqueueStopped(input.sessionId, state.revision, nextState);
6274
+ }), input.sessionId));
6275
+ };
6276
+ const requireOwnedState = async (sessionId, context) => {
6277
+ const state = await options.repository.get(sessionId);
6278
+ if (!state) throw new BackgroundSessionCallerDenied(`background session not found: ${sessionId}`);
6279
+ requireMatchingOrigin(state, context);
6280
+ return state;
6281
+ };
6282
+ const updateAndEnqueueStopped = async (sessionId, expectedRevision, nextState) => {
6283
+ const stored = await options.repository.update({
6284
+ build: () => nextState,
6285
+ expectedRevision,
6286
+ sessionId
6287
+ });
6288
+ if (stored?.phase === "stopped") await options.deliveries.enqueue({
6289
+ createdAt: options.clock.now(),
6290
+ deliveryId: terminalDeliveryId(sessionId, "stopped"),
6291
+ kind: "stopped",
6292
+ sessionId,
6293
+ text: stopNotice$1(stored)
6294
+ });
6295
+ return stored;
6296
+ };
6297
+ const resolveReconciliation = async (input) => {
6298
+ return toSummary(requireState(await serial.run(async () => {
6299
+ const state = await options.repository.get(input.sessionId);
6300
+ if (!state) throw new BackgroundSessionCallerDenied(`background session not found: ${input.sessionId}`);
6301
+ const nextState = resolveBackgroundSessionReconciliation(state, {
6302
+ now: options.clock.now(),
6303
+ outcome: input.outcome
6304
+ });
6305
+ return updateAndEnqueueStopped(input.sessionId, state.revision, nextState);
6306
+ }), input.sessionId));
6307
+ };
6308
+ return {
6309
+ list,
6310
+ resolveReconciliation,
6311
+ send,
6312
+ start,
6313
+ status,
6314
+ stop,
6315
+ wait
6316
+ };
6317
+ }
6318
+ function sessionIdFromSessionKey(sessionKey) {
6319
+ const prefix = `${createBackgroundSessionKey("")}`;
6320
+ return sessionKey.startsWith(prefix) ? sessionKey.slice(prefix.length) : void 0;
6321
+ }
6322
+ function terminalDeliveryId(sessionId, kind) {
6323
+ return `bg-final:${sessionId}:${kind}`;
6324
+ }
6325
+ function progressDeliveryId(sessionId, stepCount) {
6326
+ return `bg-progress:${sessionId}:${stepCount}`;
6327
+ }
6328
+ function requireOrigin(context) {
6329
+ if (!context.origin || !context.origin.endpointId || !context.origin.tenantKey) throw new BackgroundSessionCallerDenied("background tools require a trusted invocation origin; this run has none");
6330
+ return context.origin;
6331
+ }
6332
+ function requireSourceMessageId(context) {
6333
+ if (!context.sourceMessageId?.trim()) throw new BackgroundSessionCallerDenied("background.start requires a trusted source message id");
6334
+ return context.sourceMessageId;
6335
+ }
6336
+ function requireMatchingOrigin(state, context) {
6337
+ if (!matchesOrigin(state, context)) throw new BackgroundSessionCallerDenied("background session is not owned by this conversation");
6338
+ }
6339
+ function matchesOrigin(state, context) {
6340
+ const origin = context.origin;
6341
+ if (!origin) return false;
6342
+ if (context.agentId !== state.authority.agentId) return false;
6343
+ if (state.origin.endpointId !== origin.endpointId) return false;
6344
+ if (state.origin.tenantKey !== origin.tenantKey) return false;
6345
+ if ((state.origin.conversationId ?? void 0) !== (origin.conversationId ?? void 0)) return false;
6346
+ return true;
6347
+ }
6348
+ function requireState(state, sessionId) {
6349
+ if (!state) throw new BackgroundSessionCallerDenied(`background session not found: ${sessionId}`);
6350
+ return state;
6351
+ }
6352
+ function toSummary(state) {
6353
+ return {
6354
+ createdAt: state.createdAt,
6355
+ displayName: state.displayName,
6356
+ phase: state.phase,
6357
+ sessionId: state.sessionId,
6358
+ stepCount: state.stepCount,
6359
+ updatedAt: state.updatedAt,
6360
+ ...state.wakeAt ? { wakeAt: state.wakeAt } : {}
6361
+ };
6362
+ }
6363
+ function toDetail(state) {
6364
+ return {
6365
+ ...toSummary(state),
6366
+ ...state.cancellation ? { cancellationReason: state.cancellation.reason } : {},
6367
+ ...state.errorMessage ? { errorMessage: state.errorMessage } : {},
6368
+ parentRunId: state.parentRunId,
6369
+ pendingInput: state.pendingInput,
6370
+ ...state.reconciliationNote ? { reconciliationNote: state.reconciliationNote } : {},
6371
+ ...state.result ? { result: state.result } : {}
6372
+ };
6373
+ }
6374
+ function stopNotice$1(state) {
6375
+ return `后台会话「${state.displayName}」已停止。${state.cancellation ? `原因:${state.cancellation.reason}` : ""}`.trim();
6376
+ }
6377
+ //#endregion
6378
+ //#region src/application/background-session/background-session-repository.ts
6379
+ var BackgroundSessionRepositoryConflict = class extends Error {
6380
+ name = "BackgroundSessionRepositoryConflict";
6381
+ constructor(sessionId, message) {
6382
+ super(`${sessionId}: ${message}`);
6383
+ }
6384
+ };
6385
+ var BackgroundSessionRepositoryCorrupted = class extends Error {
6386
+ name = "BackgroundSessionRepositoryCorrupted";
6387
+ };
6388
+ function createBackgroundSessionRepository(options = {}) {
6389
+ const serial = createSerialExecutor();
6390
+ const states = /* @__PURE__ */ new Map();
6391
+ for (const state of options.initial ?? []) states.set(state.sessionId, structuredClone(state));
6392
+ const persist = async (state) => {
6393
+ if (options.persist) await options.persist(state);
6394
+ };
6395
+ return {
6396
+ create: (state) => serial.run(async () => {
6397
+ if (states.has(state.sessionId)) throw new BackgroundSessionRepositoryConflict(state.sessionId, "duplicate background session id");
6398
+ states.set(state.sessionId, structuredClone(state));
6399
+ await persist(state);
6400
+ return structuredClone(state);
6401
+ }),
6402
+ get: (sessionId) => serial.run(async () => {
6403
+ const state = states.get(sessionId);
6404
+ return state ? structuredClone(state) : void 0;
6405
+ }),
6406
+ list: (options) => serial.run(async () => {
6407
+ let results = [...states.values()];
6408
+ if (options?.phase !== void 0) results = results.filter((state) => state.phase === options.phase);
6409
+ results.sort((left, right) => left.createdAt.localeCompare(right.createdAt));
6410
+ if (options?.limit !== void 0) results = results.slice(0, options.limit);
6411
+ return results.map((state) => structuredClone(state));
6412
+ }),
6413
+ update: (input) => serial.run(async () => {
6414
+ const previous = states.get(input.sessionId);
6415
+ if (!previous) return void 0;
6416
+ if (previous.revision !== input.expectedRevision) throw new BackgroundSessionRepositoryConflict(input.sessionId, `revision conflict: expected ${input.expectedRevision}, found ${previous.revision}`);
6417
+ if (input.expectedLease !== void 0) {
6418
+ const leaseMismatch = leaseConflict(input.sessionId, previous.lease, input.expectedLease, input.now);
6419
+ if (leaseMismatch) throw new BackgroundSessionRepositoryConflict(input.sessionId, leaseMismatch);
6420
+ }
6421
+ const nextState = input.build(previous);
6422
+ const invalid = isValidBackgroundSessionTransition(previous, nextState);
6423
+ if (invalid) throw new BackgroundSessionRepositoryConflict(input.sessionId, `invalid transition: ${invalid}`);
6424
+ states.set(input.sessionId, structuredClone(nextState));
6425
+ await persist(nextState);
6426
+ return structuredClone(nextState);
6427
+ })
6428
+ };
6429
+ }
6430
+ function leaseConflict(_sessionId, lease, expected, now) {
6431
+ if (expected.kind === "absent") {
6432
+ if (lease !== void 0) return `lease conflict: expected no lease, found owner ${lease.owner} epoch ${lease.epoch}`;
6433
+ return;
6434
+ }
6435
+ if (expected.kind === "absent-or-stale") {
6436
+ if (lease !== void 0 && !isExpired(lease, now)) return `lease conflict: expected absent or stale lease, found live lease owner ${lease.owner} epoch ${lease.epoch}`;
6437
+ return;
6438
+ }
6439
+ if (lease === void 0 || lease.owner !== expected.owner || lease.epoch !== expected.epoch) return `lease conflict: expected owner ${expected.owner} epoch ${expected.epoch}`;
6440
+ }
6441
+ function isExpired(lease, now) {
6442
+ return lease !== void 0 && lease.expiresAt <= (now ?? (/* @__PURE__ */ new Date()).toISOString());
6443
+ }
6444
+ //#endregion
6445
+ //#region src/application/background-session/background-session-delivery-store.ts
6446
+ var BackgroundSessionDeliveryConflict = class extends Error {
6447
+ name = "BackgroundSessionDeliveryConflict";
6448
+ constructor(deliveryId, message) {
6449
+ super(`${deliveryId}: ${message}`);
6450
+ }
6451
+ };
6452
+ function createBackgroundSessionDeliveryStore(options = {}) {
6453
+ const serial = createSerialExecutor();
6454
+ const records = /* @__PURE__ */ new Map();
6455
+ for (const record of options.initial ?? []) records.set(record.deliveryId, structuredClone(record));
6456
+ const persist = async (record) => {
6457
+ if (options.persist) await options.persist(record);
6458
+ };
6459
+ return {
6460
+ enqueue: (input) => serial.run(async () => {
6461
+ const existing = records.get(input.deliveryId);
6462
+ if (existing) return structuredClone(existing);
6463
+ const record = {
6464
+ createdAt: input.createdAt,
6465
+ deliveryId: input.deliveryId,
6466
+ kind: input.kind,
6467
+ revision: 1,
6468
+ sessionId: input.sessionId,
6469
+ state: "pending",
6470
+ text: input.text
6471
+ };
6472
+ records.set(input.deliveryId, structuredClone(record));
6473
+ await persist(record);
6474
+ return structuredClone(record);
6475
+ }),
6476
+ pending: () => serial.run(async () => {
6477
+ return [...records.values()].filter((record) => record.state === "pending").sort((left, right) => left.createdAt.localeCompare(right.createdAt)).map((record) => structuredClone(record));
6478
+ }),
6479
+ markDelivered: (deliveryId, providerMessageId) => serial.run(async () => {
6480
+ const previous = records.get(deliveryId);
6481
+ if (!previous) return void 0;
6482
+ if (previous.state !== "pending") return structuredClone(previous);
6483
+ const nextState = {
6484
+ ...previous,
6485
+ providerMessageId,
6486
+ revision: previous.revision + 1,
6487
+ state: "delivered"
6488
+ };
6489
+ records.set(deliveryId, structuredClone(nextState));
6490
+ await persist(nextState);
6491
+ return structuredClone(nextState);
6492
+ })
6493
+ };
6494
+ }
6495
+ //#endregion
6496
+ //#region src/application/background-session/background-session-supervisor.ts
6497
+ function createBackgroundSessionSupervisor(options) {
6498
+ const owner = `supervisor:${randomUUID()}`;
6499
+ const inFlight = /* @__PURE__ */ new Map();
6500
+ const stepControllers = /* @__PURE__ */ new Map();
6501
+ const counters = {
6502
+ completed: 0,
6503
+ deliveriesDelivered: 0,
6504
+ failed: 0,
6505
+ started: 0,
6506
+ staleLeaseEventsDropped: 0,
6507
+ stopped: 0
6508
+ };
6509
+ let repositoryHealthy = true;
6510
+ let snapshot = emptyPhaseCounts();
6511
+ let oldestDueAt;
6512
+ const requeuedInPass = /* @__PURE__ */ new Set();
6513
+ const refreshSnapshot = async () => {
6514
+ const counts = {
6515
+ completed: 0,
6516
+ failed: 0,
6517
+ "input-required": 0,
6518
+ queued: 0,
6519
+ "reconciliation-required": 0,
6520
+ running: 0,
6521
+ stopped: 0,
6522
+ stopping: 0,
6523
+ waiting: 0
6524
+ };
6525
+ let oldest;
6526
+ const now = options.clock.now();
6527
+ for (const session of await options.repository.list()) {
6528
+ counts[session.phase] += 1;
6529
+ if (isBackgroundSessionDue(session, now)) {
6530
+ const dueAt = session.wakeAt ?? session.createdAt;
6531
+ if (oldest === void 0 || dueAt < oldest) oldest = dueAt;
6532
+ }
6533
+ }
6534
+ snapshot = Object.freeze(counts);
6535
+ oldestDueAt = oldest;
6536
+ };
6537
+ const supervisionPass = async () => {
6538
+ try {
6539
+ await resolveStaleLeases();
6540
+ await expireLifetime();
6541
+ await stopStoppingSessions();
6542
+ await drainDeliveries();
6543
+ await startDueSessions();
6544
+ await refreshSnapshot();
6545
+ repositoryHealthy = true;
6546
+ } catch (error) {
6547
+ repositoryHealthy = false;
6548
+ throw error;
6549
+ }
6550
+ };
6551
+ const resolveStaleLeases = async () => {
6552
+ const now = options.clock.now();
6553
+ for (const session of await options.repository.list()) {
6554
+ if (session.lease === void 0 || inFlight.has(session.sessionId)) continue;
6555
+ if (!(session.lease.owner !== owner || isBackgroundSessionLeaseExpired(session, now))) continue;
6556
+ counters.staleLeaseEventsDropped += 1;
6557
+ const expectedLease = {
6558
+ kind: "held",
6559
+ owner: session.lease.owner,
6560
+ epoch: session.lease.epoch
6561
+ };
6562
+ if (session.phase === "running") {
6563
+ await options.repository.update({
6564
+ build: (state) => requeueInterruptedBackgroundSessionStep(state, {
6565
+ now,
6566
+ wakeText: interruptedStepWakeText(state)
6567
+ }),
6568
+ expectedLease,
6569
+ expectedRevision: session.revision,
6570
+ sessionId: session.sessionId
6571
+ });
6572
+ requeuedInPass.add(session.sessionId);
6573
+ continue;
6574
+ }
6575
+ if (session.phase === "stopping") {
6576
+ await completeStop(session, expectedLease);
6577
+ continue;
6578
+ }
6579
+ await options.repository.update({
6580
+ build: (state) => releaseBackgroundSessionLease(state),
6581
+ expectedLease,
6582
+ expectedRevision: session.revision,
6583
+ sessionId: session.sessionId
6584
+ });
6585
+ }
6586
+ };
6587
+ const expireLifetime = async () => {
6588
+ const now = options.clock.now();
6589
+ const cutoff = Date.parse(now) - options.config.sessionLifetimeMs;
6590
+ for (const session of await options.repository.list()) {
6591
+ if (isBackgroundSessionTerminalPhase(session.phase)) continue;
6592
+ if (Date.parse(session.createdAt) > cutoff) continue;
6593
+ const nextState = requestBackgroundSessionStop(session, {
6594
+ now,
6595
+ reason: "background session lifetime exceeded"
6596
+ });
6597
+ const stored = await options.repository.update({
6598
+ build: () => nextState,
6599
+ expectedRevision: session.revision,
6600
+ sessionId: session.sessionId
6601
+ });
6602
+ if (stored?.phase === "stopped") {
6603
+ counters.stopped += 1;
6604
+ await enqueueDelivery(stored.sessionId, "stopped", stopNotice(stored), stored);
6605
+ } else stepControllers.get(session.sessionId)?.abort(/* @__PURE__ */ new Error("background session lifetime exceeded"));
6606
+ }
6607
+ };
6608
+ const stopStoppingSessions = async () => {
6609
+ for (const session of await options.repository.list()) {
6610
+ if (session.phase !== "stopping") continue;
6611
+ if (inFlight.has(session.sessionId)) {
6612
+ stepControllers.get(session.sessionId)?.abort(/* @__PURE__ */ new Error("background session stop requested"));
6613
+ continue;
6614
+ }
6615
+ const expectedLease = session.lease ? {
6616
+ kind: "held",
6617
+ owner: session.lease.owner,
6618
+ epoch: session.lease.epoch
6619
+ } : { kind: "absent" };
6620
+ await completeStop(session, expectedLease);
6621
+ }
6622
+ };
6623
+ const completeStop = async (state, expectedLease) => {
6624
+ const stopped = await options.repository.update({
6625
+ build: (current) => completeBackgroundSessionStop(current, { now: options.clock.now() }),
6626
+ expectedLease,
6627
+ expectedRevision: state.revision,
6628
+ sessionId: state.sessionId
6629
+ });
6630
+ if (stopped) {
6631
+ counters.stopped += 1;
6632
+ await enqueueDelivery(stopped.sessionId, "stopped", stopNotice(stopped), stopped);
6633
+ }
6634
+ };
6635
+ const startDueSessions = async () => {
6636
+ const now = options.clock.now();
6637
+ for (const session of await options.repository.list()) {
6638
+ if (inFlight.size >= options.config.maxConcurrentSessions) break;
6639
+ if (inFlight.has(session.sessionId)) continue;
6640
+ if (!isBackgroundSessionDue(session, now)) continue;
6641
+ if (session.lease !== void 0 && !isBackgroundSessionLeaseExpired(session, now)) continue;
6642
+ const step = runStep(session).finally(() => {
6643
+ inFlight.delete(session.sessionId);
6644
+ });
6645
+ inFlight.set(session.sessionId, step);
6646
+ step.catch((error) => {
6647
+ options.onError?.(error);
6648
+ });
6649
+ }
6650
+ };
6651
+ const runStep = async (session) => {
6652
+ const now = options.clock.now();
6653
+ const stepCount = session.stepCount + 1;
6654
+ const wakeText = requeuedInPass.has(session.sessionId) ? interruptedStepWakeText(session) : buildWakeText(session);
6655
+ requeuedInPass.delete(session.sessionId);
6656
+ const lease = {
6657
+ epoch: (session.lease?.epoch ?? 0) + 1,
6658
+ expiresAt: toIso(Date.parse(now) + options.config.leaseMs),
6659
+ owner
6660
+ };
6661
+ const claimed = await options.repository.update({
6662
+ build: (state) => claimBackgroundSession(state, {
6663
+ lease,
6664
+ now,
6665
+ stepRunId: `bg-step:${session.sessionId}:${stepCount}`,
6666
+ wakeText
6667
+ }),
6668
+ expectedLease: { kind: "absent-or-stale" },
6669
+ expectedRevision: session.revision,
6670
+ now,
6671
+ sessionId: session.sessionId
6672
+ });
6673
+ if (!claimed) return;
6674
+ counters.started += 1;
6675
+ const controller = new AbortController();
6676
+ stepControllers.set(session.sessionId, controller);
6677
+ let outcome;
6678
+ try {
6679
+ let settled = false;
6680
+ const renewal = (async () => {
6681
+ while (!settled) {
6682
+ await Effect.runPromise(options.sleep(options.config.leaseRenewalIntervalMs)).catch(() => void 0);
6683
+ if (settled) break;
6684
+ try {
6685
+ const current = await options.repository.get(session.sessionId);
6686
+ if (!current || current.lease === void 0 || current.lease.owner !== owner || current.lease.epoch !== lease.epoch) {
6687
+ controller.abort(/* @__PURE__ */ new Error("background session lease was lost"));
6688
+ break;
6689
+ }
6690
+ if (!await options.repository.update({
6691
+ build: (state) => renewBackgroundSessionLease(state, {
6692
+ expiresAt: toIso(Date.parse(options.clock.now()) + options.config.leaseMs),
6693
+ epoch: lease.epoch,
6694
+ owner
6695
+ }),
6696
+ expectedLease: {
6697
+ kind: "held",
6698
+ owner,
6699
+ epoch: lease.epoch
6700
+ },
6701
+ expectedRevision: current.revision,
6702
+ sessionId: session.sessionId
6703
+ })) controller.abort(/* @__PURE__ */ new Error("background session lease renewal was denied"));
6704
+ } catch {
6705
+ controller.abort(/* @__PURE__ */ new Error("background session lease renewal failed"));
6706
+ }
6707
+ }
6708
+ })();
6709
+ outcome = await options.runStep({
6710
+ session: claimed,
6711
+ signal: controller.signal,
6712
+ wakeText
6713
+ }).then((result) => ({
6714
+ ok: true,
6715
+ result
6716
+ }), (error) => ({
6717
+ error,
6718
+ ok: false
6719
+ }));
6720
+ settled = true;
6721
+ renewal.catch(() => void 0);
6722
+ } finally {
6723
+ stepControllers.delete(session.sessionId);
6724
+ }
6725
+ try {
6726
+ await commitStep(claimed, outcome, lease);
6727
+ } catch (error) {
6728
+ options.onError?.(error);
6729
+ await requeueAfterFailedCommit(session.sessionId, lease, error);
6730
+ }
6731
+ };
6732
+ const requeueAfterFailedCommit = async (sessionId, lease, error) => {
6733
+ try {
6734
+ const current = await options.repository.get(sessionId);
6735
+ if (!current || current.phase !== "running") return;
6736
+ await options.repository.update({
6737
+ build: (state) => requeueInterruptedBackgroundSessionStep(state, {
6738
+ now: options.clock.now(),
6739
+ wakeText: `步骤结果提交失败(${errorMessageOf(error)}),将重试`
6740
+ }),
6741
+ expectedLease: {
6742
+ kind: "held",
6743
+ owner: lease.owner,
6744
+ epoch: lease.epoch
6745
+ },
6746
+ expectedRevision: current.revision,
6747
+ sessionId
6748
+ });
6749
+ } catch (commitError) {
6750
+ options.onError?.(commitError);
6751
+ }
6752
+ };
6753
+ const commitStep = async (claimed, outcome, lease) => {
6754
+ const now = options.clock.now();
6755
+ const current = await options.repository.get(claimed.sessionId);
6756
+ if (!current) return;
6757
+ const expectedLease = {
6758
+ kind: "held",
6759
+ owner: lease.owner,
6760
+ epoch: lease.epoch
6761
+ };
6762
+ if (current.phase === "waiting" || current.phase === "input-required") {
6763
+ const text = outcome.ok ? outcome.result.finalText : "";
6764
+ await options.repository.update({
6765
+ build: (state) => releaseBackgroundSessionLease(state),
6766
+ expectedLease,
6767
+ expectedRevision: current.revision,
6768
+ sessionId: current.sessionId
6769
+ });
6770
+ if (text.trim() !== "") await enqueueDelivery(current.sessionId, "progress", text, current);
6771
+ return;
6772
+ }
6773
+ if (current.phase === "stopping") {
6774
+ await completeStop(current, expectedLease);
6775
+ return;
6776
+ }
6777
+ if (current.phase !== "running") return;
6778
+ if (outcome.ok) {
6779
+ const completed = await options.repository.update({
6780
+ build: (state) => completeBackgroundSessionStep(state, {
6781
+ now,
6782
+ stepRunId: outcome.result.runId,
6783
+ text: outcome.result.finalText
6784
+ }),
6785
+ expectedLease,
6786
+ expectedRevision: current.revision,
6787
+ sessionId: current.sessionId
6788
+ });
6789
+ if (completed) {
6790
+ counters.completed += 1;
6791
+ await enqueueDelivery(completed.sessionId, "final", completed.result?.text ?? "", completed);
6792
+ }
6793
+ return;
6794
+ }
6795
+ const errorMessage = errorMessageOf(outcome.error);
6796
+ if (isFencedOperationError(outcome.error)) {
6797
+ const parked = await options.repository.update({
6798
+ build: (state) => parkBackgroundSessionForReconciliation(state, {
6799
+ now,
6800
+ reason: `step failed on a fenced operation: ${errorMessage}`
6801
+ }),
6802
+ expectedLease,
6803
+ expectedRevision: current.revision,
6804
+ sessionId: current.sessionId
6805
+ });
6806
+ if (parked) await enqueueDelivery(parked.sessionId, "reconciliation", `后台会话「${parked.displayName}」需要人工对账:${parked.reconciliationNote ?? errorMessage}`, parked);
6807
+ return;
6808
+ }
6809
+ if (isRetryableStepError(outcome.error) && current.consecutiveFailures + 1 < options.config.maxConsecutiveFailures) {
6810
+ const backoff = options.config.retryBackoffMs * 2 ** current.consecutiveFailures;
6811
+ await options.repository.update({
6812
+ build: (state) => failBackgroundSessionStep(state, {
6813
+ errorMessage,
6814
+ now,
6815
+ retryable: true,
6816
+ wakeAt: toIso(Date.parse(now) + backoff)
6817
+ }),
6818
+ expectedLease,
6819
+ expectedRevision: current.revision,
6820
+ sessionId: current.sessionId
6821
+ });
6822
+ return;
6823
+ }
6824
+ const failed = await options.repository.update({
6825
+ build: (state) => failBackgroundSessionStep(state, {
6826
+ errorMessage,
6827
+ now,
6828
+ retryable: false
6829
+ }),
6830
+ expectedLease,
6831
+ expectedRevision: current.revision,
6832
+ sessionId: current.sessionId
6833
+ });
6834
+ if (failed) {
6835
+ counters.failed += 1;
6836
+ await enqueueDelivery(failed.sessionId, "failed", `后台会话「${failed.displayName}」失败:${failed.errorMessage ?? errorMessage}`, failed);
6837
+ }
6838
+ };
6839
+ const drainDeliveries = async () => {
6840
+ const pending = await options.deliveries.pending();
6841
+ for (const record of pending) try {
6842
+ const { providerMessageId } = await options.deliver(record);
6843
+ if (await options.deliveries.markDelivered(record.deliveryId, providerMessageId)) counters.deliveriesDelivered += 1;
6844
+ } catch (error) {
6845
+ options.onError?.(error);
6846
+ }
6847
+ };
6848
+ const enqueueDelivery = async (sessionId, kind, text, session) => {
6849
+ if (text.trim() === "") return;
6850
+ const deliveryId = kind === "progress" ? progressDeliveryId(sessionId, session.stepCount) : terminalDeliveryId(sessionId, kind);
6851
+ await options.deliveries.enqueue({
6852
+ createdAt: options.clock.now(),
6853
+ deliveryId,
6854
+ kind,
6855
+ sessionId,
6856
+ text
6857
+ });
6858
+ };
6859
+ const buildWakeText = (session) => {
6860
+ if (session.wakeCount === 0) return `你在一个后台会话中开始工作。任务:\n${session.prompt}\n\n继续工作直到给出最终结果;如果必须等待外部变化或用户输入,调用 background.wait。`;
6861
+ const parts = [`后台会话第 ${session.stepCount + 1} 步。任务:\n${session.prompt}`, "继续先前的工作(历史在会话记录中)。"];
6862
+ if (session.pendingInput.length > 0) parts.push(`用户新的输入:\n${session.pendingInput.join("\n---\n")}`);
6863
+ parts.push("完成后给出最终结果;需要等待时调用 background.wait。");
6864
+ return parts.join("\n\n");
6865
+ };
6866
+ const interruptedStepWakeText = (session) => `你的上一步在后台会话中被守护进程重启打断,未产生确定的工具副作用(副作用由工具账本保护)。继续先前的工作(历史在会话记录中)。` + (session.pendingInput.length > 0 ? `\n\n用户新的输入:\n${session.pendingInput.join("\n---\n")}` : "");
6867
+ const tick = () => Effect.promise(() => supervisionPass());
6868
+ const loop = createPeriodicEffectLoop({
6869
+ intervalMs: options.config.intervalMs,
6870
+ onError: (error) => {
6871
+ options.onError?.(error);
6872
+ },
6873
+ run: tick,
6874
+ sleep: options.sleep
6875
+ });
6876
+ const statusOf = () => Object.freeze({
6877
+ capacity: options.config.maxConcurrentSessions,
6878
+ counters: Object.freeze({ ...counters }),
6879
+ inFlightSteps: inFlight.size,
6880
+ ...oldestDueAt === void 0 ? {} : { oldestDueAt },
6881
+ owner,
6882
+ phaseCounts: snapshot,
6883
+ reconciliationCount: snapshot["reconciliation-required"],
6884
+ repositoryHealthy,
6885
+ running: loop.running()
6886
+ });
6887
+ return {
6888
+ recover: tick,
6889
+ start: () => Effect.sync(() => loop.start()),
6890
+ status: statusOf,
6891
+ stop: () => Effect.promise(async () => {
6892
+ for (const controller of stepControllers.values()) controller.abort(/* @__PURE__ */ new Error("background session supervisor is stopping"));
6893
+ await Promise.allSettled(inFlight.values());
6894
+ await loop.stop();
6895
+ }),
6896
+ tick
6897
+ };
6898
+ }
6899
+ function isFencedOperationError(error) {
6900
+ const message = errorMessageOf(error).toLowerCase();
6901
+ return message.includes("fenced") || message.includes("reconciliation-required");
6902
+ }
6903
+ function isRetryableStepError(error) {
6904
+ if (!(error instanceof Error)) return false;
6905
+ if (error.name === "AgentRunCancelled") return true;
6906
+ if (error.name === "AgentHarnessBusy" || error.name === "AgentInstanceBusy") return true;
6907
+ return false;
6908
+ }
6909
+ function errorMessageOf(error) {
6910
+ return error instanceof Error ? error.message : String(error);
6911
+ }
6912
+ function toIso(timeMs) {
6913
+ return new Date(timeMs).toISOString();
6914
+ }
6915
+ function stopNotice(session) {
6916
+ return `后台会话「${session.displayName}」已停止。${session.cancellation ? `原因:${session.cancellation.reason}` : ""}`.trim();
6917
+ }
6918
+ function emptyPhaseCounts() {
6919
+ return Object.freeze({
6920
+ completed: 0,
6921
+ failed: 0,
6922
+ "input-required": 0,
6923
+ queued: 0,
6924
+ "reconciliation-required": 0,
6925
+ running: 0,
6926
+ stopped: 0,
6927
+ stopping: 0,
6928
+ waiting: 0
6929
+ });
6930
+ }
6931
+ //#endregion
6932
+ //#region src/application/background-session/background-session-tools.ts
6933
+ function createBackgroundSessionHostTools(options) {
6934
+ const contracts = createBackgroundSessionToolContracts();
6935
+ const executors = new Map(contracts.map((contract) => [contract.id, createExecutor(contract, options)]));
6936
+ return contracts.map((contract) => Object.freeze({
6937
+ ...contract,
6938
+ createExecutor: () => ({ execute: (input, context) => asToolInputRejection$1(() => executors.get(contract.id)(input, context)) }),
6939
+ replayCompleted: (input, completedResult, context) => asToolInputRejection$1(() => replayCompleted(contract.id, input, completedResult, context, options))
6940
+ }));
6941
+ }
6942
+ function createExecutor(contract, options) {
6943
+ switch (contract.id) {
6944
+ case "background.start": return (input, context) => {
6945
+ const { prompt, displayName } = readObject(input, ["prompt", "displayName"]);
6946
+ if (typeof prompt !== "string" || prompt.trim() === "") throw new BackgroundSessionCallerDenied("background.start requires a non-empty prompt");
6947
+ if (displayName !== void 0 && typeof displayName !== "string") throw new BackgroundSessionCallerDenied("background.start displayName must be a string");
6948
+ const sessionId = options.createSessionId();
6949
+ return options.service.start({
6950
+ authority: buildAuthority(options.definition, context, sessionId),
6951
+ context,
6952
+ sessionId,
6953
+ ...displayName === void 0 ? {} : { displayName },
6954
+ prompt
6955
+ });
6956
+ };
6957
+ case "background.wait": return (input, context) => {
6958
+ const { delayMs, reason, until } = readObject(input, [
6959
+ "delayMs",
6960
+ "reason",
6961
+ "until"
6962
+ ]);
6963
+ return options.service.wait({
6964
+ context,
6965
+ ...delayMs === void 0 ? {} : { delayMs: readInteger(delayMs, "delayMs") },
6966
+ ...reason === void 0 ? {} : { reason: readString(reason, "reason") },
6967
+ ...until === void 0 ? {} : { until: readString(until, "until") }
6968
+ });
6969
+ };
6970
+ case "background.list": return async (input, context) => {
6971
+ const { limit, phase } = readObject(input, ["limit", "phase"]);
6972
+ return { sessions: await options.service.list({
6973
+ context,
6974
+ ...limit === void 0 ? {} : { limit: readInteger(limit, "limit") },
6975
+ ...phase === void 0 ? {} : { phase: readPhase(phase) }
6976
+ }) };
6977
+ };
6978
+ case "background.status": return (input, context) => {
6979
+ const { sessionId } = readObject(input, ["sessionId"]);
6980
+ return options.service.status({
6981
+ context,
6982
+ sessionId: readString(sessionId, "sessionId")
6983
+ });
6984
+ };
6985
+ case "background.send": return (input, context) => {
6986
+ const { message, sessionId } = readObject(input, ["message", "sessionId"]);
6987
+ return options.service.send({
6988
+ context,
6989
+ message: readString(message, "message"),
6990
+ sessionId: readString(sessionId, "sessionId")
6991
+ });
6992
+ };
6993
+ case "background.stop": return (input, context) => {
6994
+ const { reason, sessionId } = readObject(input, ["reason", "sessionId"]);
6995
+ return options.service.stop({
6996
+ context,
6997
+ ...reason === void 0 ? {} : { reason: readString(reason, "reason") },
6998
+ sessionId: readString(sessionId, "sessionId")
6999
+ });
7000
+ };
7001
+ default: throw new BackgroundSessionCallerDenied(`unknown background tool: ${contract.id}`);
7002
+ }
7003
+ }
7004
+ function replayCompleted(toolId, input, completedResult, context, options) {
7005
+ switch (toolId) {
7006
+ case "background.start": {
7007
+ const sessionId = readReplayedSessionId(completedResult);
7008
+ return options.service.status({
7009
+ context,
7010
+ sessionId
7011
+ });
7012
+ }
7013
+ case "background.wait": return completedResult;
7014
+ case "background.list": return completedResult;
7015
+ case "background.status": {
7016
+ const sessionId = readReplayedSessionId(input);
7017
+ return options.service.status({
7018
+ context,
7019
+ sessionId
7020
+ });
7021
+ }
7022
+ case "background.send": {
7023
+ const sessionId = readReplayedSessionId(input);
7024
+ return options.service.status({
7025
+ context,
7026
+ sessionId
7027
+ });
7028
+ }
7029
+ case "background.stop": {
7030
+ const sessionId = readReplayedSessionId(input);
7031
+ return options.service.status({
7032
+ context,
7033
+ sessionId
7034
+ });
7035
+ }
7036
+ default: return completedResult;
7037
+ }
7038
+ }
7039
+ function buildAuthority(definition, context, sessionId) {
7040
+ return Object.freeze({
7041
+ agentId: definition.agentId,
7042
+ memoryScopes: definition.memory.scopes,
7043
+ policyEpoch: context.policyEpoch,
7044
+ profileRevision: definition.profileRevision,
7045
+ ...definition.projectSpaceId === void 0 ? {} : { projectSpaceId: definition.projectSpaceId },
7046
+ sessionKey: createBackgroundSessionKey(sessionId),
7047
+ toolGrantRevision: definition.toolGrantSet.revision
7048
+ });
7049
+ }
7050
+ function readObject(input, allowed) {
7051
+ if (input === null || typeof input !== "object" || Array.isArray(input)) throw new BackgroundSessionCallerDenied("background tool input must be an object");
7052
+ const record = input;
7053
+ const unknown = Object.keys(record).find((key) => !allowed.includes(key));
7054
+ if (unknown) throw new BackgroundSessionCallerDenied(`field is not allowed: ${unknown}`);
7055
+ return record;
7056
+ }
7057
+ function readString(value, name) {
7058
+ if (typeof value !== "string" || value.trim() === "") throw new BackgroundSessionCallerDenied(`${name} must be a non-empty string`);
7059
+ return value;
7060
+ }
7061
+ function readInteger(value, name) {
7062
+ if (!Number.isSafeInteger(value) || value < 1) throw new BackgroundSessionCallerDenied(`${name} must be a positive integer`);
7063
+ return value;
7064
+ }
7065
+ function readPhase(value) {
7066
+ const phases = [
7067
+ "queued",
7068
+ "running",
7069
+ "waiting",
7070
+ "input-required",
7071
+ "stopping",
7072
+ "stopped",
7073
+ "completed",
7074
+ "failed",
7075
+ "reconciliation-required"
7076
+ ];
7077
+ if (typeof value !== "string" || !phases.includes(value)) throw new BackgroundSessionCallerDenied(`phase must be one of ${phases.join(", ")}`);
7078
+ return value;
7079
+ }
7080
+ function readReplayedSessionId(value) {
7081
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new BackgroundSessionCallerDenied("replayed background tool result is invalid");
7082
+ return readString(value.sessionId, "sessionId");
7083
+ }
7084
+ async function asToolInputRejection$1(operation) {
7085
+ try {
7086
+ return await operation();
7087
+ } catch (error) {
7088
+ if (error instanceof BackgroundSessionCallerDenied || error instanceof BackgroundSessionTransitionDenied) throw new RivusToolInputRejected(error.message);
7089
+ throw error;
7090
+ }
7091
+ }
7092
+ //#endregion
7093
+ //#region src/infrastructure/persistence/jsonl-snapshot-store.ts
7094
+ var JsonlSnapshotStoreCorrupted = class extends Error {
7095
+ name = "JsonlSnapshotStoreCorrupted";
7096
+ };
7097
+ async function loadJsonlSnapshotStore(options) {
7098
+ const raw = await readPersistenceFile(options.filePath);
7099
+ if (raw === void 0) return [];
7100
+ const lines = raw.split("\n");
7101
+ const latest = /* @__PURE__ */ new Map();
7102
+ let lineCount = 0;
7103
+ for (let index = 0; index < lines.length; index += 1) {
7104
+ const line = lines[index] ?? "";
7105
+ if (!line.trim()) continue;
7106
+ lineCount += 1;
7107
+ let parsed;
7108
+ try {
7109
+ parsed = JSON.parse(line);
7110
+ } catch {
7111
+ if (index === lines.length - 1) {
7112
+ await truncateAtLine(options.filePath, lines, index);
7113
+ break;
7114
+ }
7115
+ throw new JsonlSnapshotStoreCorrupted(`${options.errorLabel} JSONL at line ${index + 1} is not valid JSON`);
7116
+ }
7117
+ if (!isRecord$4(parsed) || parsed.version !== options.version) throw new JsonlSnapshotStoreCorrupted(`unsupported ${options.errorLabel} version at line ${index + 1}`);
7118
+ if (isRecord$4(parsed.snapshot)) {
7119
+ if (!Array.isArray(parsed.snapshot.records)) throw new JsonlSnapshotStoreCorrupted(`invalid ${options.errorLabel} snapshot at line ${index + 1}`);
7120
+ latest.clear();
7121
+ for (const value of parsed.snapshot.records) {
7122
+ if (!options.isRecord(value)) throw new JsonlSnapshotStoreCorrupted(`invalid ${options.errorLabel} snapshot at line ${index + 1}`);
7123
+ latest.set(options.keyOf(value), structuredClone(value));
7124
+ }
7125
+ continue;
7126
+ }
7127
+ if (!options.isRecord(parsed.record)) throw new JsonlSnapshotStoreCorrupted(`invalid ${options.errorLabel} record at line ${index + 1}`);
7128
+ const record = parsed.record;
7129
+ const previous = latest.get(options.keyOf(record));
7130
+ const invalid = options.validateSequence(previous, record, index + 1);
7131
+ if (invalid) throw new JsonlSnapshotStoreCorrupted(`invalid ${options.errorLabel} ${invalid} at line ${index + 1}`);
7132
+ latest.set(options.keyOf(record), structuredClone(record));
7133
+ }
7134
+ if (lineCount >= options.compactThreshold) await writeJsonlSnapshot(options.filePath, options.version, [...latest.values()]);
7135
+ return [...latest.values()];
7136
+ }
7137
+ async function appendJsonlRecord(filePath, version, record) {
7138
+ await mkdir(dirname(filePath), { recursive: true });
7139
+ await appendFile(filePath, `${JSON.stringify({
7140
+ record,
7141
+ version
7142
+ })}\n`, "utf8");
7143
+ }
7144
+ async function writeJsonlSnapshot(filePath, version, records) {
7145
+ await mkdir(dirname(filePath), { recursive: true });
7146
+ await writeFile(filePath, `${JSON.stringify({
7147
+ snapshot: { records },
7148
+ version
7149
+ })}\n`, "utf8");
7150
+ }
7151
+ async function truncateAtLine(filePath, lines, lineIndex) {
7152
+ let offset = 0;
7153
+ for (let index = 0; index < lineIndex; index += 1) offset += (lines[index] ?? "").length + 1;
7154
+ await truncate(filePath, offset);
7155
+ }
7156
+ //#endregion
7157
+ //#region src/infrastructure/persistence/jsonl-background-session-repository.ts
7158
+ const BACKGROUND_SESSION_JSONL_VERSION = 1;
7159
+ async function openJsonlBackgroundSessionRepository(options) {
7160
+ return createBackgroundSessionRepository({
7161
+ initial: await loadJsonlSnapshotStore({
7162
+ compactThreshold: options.compactThreshold ?? 5e3,
7163
+ errorLabel: "background session",
7164
+ filePath: options.filePath,
7165
+ isRecord: isBackgroundSessionState,
7166
+ keyOf: (record) => record.sessionId,
7167
+ validateSequence: validateSessionSequence,
7168
+ version: 1
7169
+ }),
7170
+ persist: (record) => appendJsonlRecord(options.filePath, 1, record)
7171
+ });
7172
+ }
7173
+ function validateSessionSequence(previous, record, _lineNumber) {
7174
+ if (previous === void 0) {
7175
+ if (record.revision !== 1 || record.phase !== "queued") return "first record";
7176
+ return;
7177
+ }
7178
+ if (record.revision !== previous.revision + 1) return "revision";
7179
+ return isValidBackgroundSessionTransition(previous, record);
7180
+ }
7181
+ function isBackgroundSessionState(value) {
7182
+ if (!isRecord$4(value)) return false;
7183
+ const phases = [
7184
+ "queued",
7185
+ "running",
7186
+ "waiting",
7187
+ "input-required",
7188
+ "stopping",
7189
+ "stopped",
7190
+ "completed",
7191
+ "failed",
7192
+ "reconciliation-required"
7193
+ ];
7194
+ if (typeof value.sessionId !== "string" || value.sessionId.trim() === "") return false;
7195
+ if (typeof value.displayName !== "string") return false;
7196
+ if (!phases.includes(value.phase)) return false;
7197
+ if (!Number.isInteger(value.revision) || value.revision < 1) return false;
7198
+ if (typeof value.parentRunId !== "string" || value.parentRunId.trim() === "") return false;
7199
+ if (typeof value.sourceMessageId !== "string" || value.sourceMessageId.trim() === "") return false;
7200
+ if (!isRecord$4(value.origin)) return false;
7201
+ const origin = value.origin;
7202
+ if (typeof origin.endpointId !== "string" || origin.endpointId.trim() === "") return false;
7203
+ if (typeof origin.tenantKey !== "string") return false;
7204
+ if (!Array.isArray(origin.allowedActorOpenIds) || origin.allowedActorOpenIds.some((id) => typeof id !== "string")) return false;
7205
+ if (origin.conversationId !== void 0 && typeof origin.conversationId !== "string") return false;
7206
+ if (origin.memory !== void 0 && !isRecord$4(origin.memory)) return false;
7207
+ if (!isRecord$4(value.authority)) return false;
7208
+ const authority = value.authority;
7209
+ if (typeof authority.agentId !== "string" || authority.agentId.trim() === "") return false;
7210
+ if (typeof authority.profileRevision !== "string" || authority.profileRevision.trim() === "") return false;
7211
+ if (typeof authority.toolGrantRevision !== "string" || authority.toolGrantRevision.trim() === "") return false;
7212
+ if (!Number.isInteger(authority.policyEpoch) || authority.policyEpoch < 0) return false;
7213
+ if (!Array.isArray(authority.memoryScopes) || authority.memoryScopes.some((scope) => typeof scope !== "string")) return false;
7214
+ if (typeof authority.sessionKey !== "string" || authority.sessionKey.trim() === "") return false;
7215
+ if (authority.projectSpaceId !== void 0 && typeof authority.projectSpaceId !== "string") return false;
7216
+ if (typeof value.prompt !== "string") return false;
7217
+ if (!Number.isInteger(value.stepCount) || value.stepCount < 0) return false;
7218
+ if (!Number.isInteger(value.wakeCount) || value.wakeCount < 0) return false;
7219
+ if (!Number.isInteger(value.consecutiveFailures) || value.consecutiveFailures < 0) return false;
7220
+ if (!Array.isArray(value.pendingInput) || value.pendingInput.some((entry) => typeof entry !== "string")) return false;
7221
+ if (value.currentStepRunId !== void 0 && typeof value.currentStepRunId !== "string") return false;
7222
+ if (value.lastWakeText !== void 0 && typeof value.lastWakeText !== "string") return false;
7223
+ if (value.wakeAt !== void 0 && typeof value.wakeAt !== "string") return false;
7224
+ if (value.errorMessage !== void 0 && typeof value.errorMessage !== "string") return false;
7225
+ if (value.reconciliationNote !== void 0 && typeof value.reconciliationNote !== "string") return false;
7226
+ if (typeof value.createdAt !== "string" || !isIso(value.createdAt)) return false;
7227
+ if (typeof value.updatedAt !== "string" || !isIso(value.updatedAt)) return false;
7228
+ if (value.lease !== void 0 && !isLease(value.lease)) return false;
7229
+ if (value.cancellation !== void 0) {
7230
+ if (!isRecord$4(value.cancellation)) return false;
7231
+ if (typeof value.cancellation.reason !== "string" || !isIso(value.cancellation.requestedAt)) return false;
7232
+ }
7233
+ if (value.result !== void 0) {
7234
+ if (!isRecord$4(value.result)) return false;
7235
+ if (typeof value.result.text !== "string" || typeof value.result.stepRunId !== "string" || !isIso(value.result.completedAt)) return false;
7236
+ }
7237
+ return true;
7238
+ }
7239
+ function isLease(value) {
7240
+ if (!isRecord$4(value)) return false;
7241
+ return typeof value.owner === "string" && value.owner.trim() !== "" && Number.isInteger(value.epoch) && value.epoch >= 0 && isIso(value.expiresAt);
7242
+ }
7243
+ function isIso(value) {
7244
+ return typeof value === "string" && Number.isFinite(Date.parse(value));
7245
+ }
7246
+ //#endregion
7247
+ //#region src/infrastructure/persistence/jsonl-background-session-delivery-store.ts
7248
+ const BACKGROUND_SESSION_DELIVERY_JSONL_VERSION = 1;
7249
+ async function openJsonlBackgroundSessionDeliveryStore(options) {
7250
+ return createBackgroundSessionDeliveryStore({
7251
+ initial: await loadJsonlSnapshotStore({
7252
+ compactThreshold: options.compactThreshold ?? 2e3,
7253
+ errorLabel: "background session delivery",
7254
+ filePath: options.filePath,
7255
+ isRecord: isDeliveryRecord,
7256
+ keyOf: (record) => record.deliveryId,
7257
+ validateSequence: validateDeliverySequence,
7258
+ version: 1
7259
+ }),
7260
+ persist: (record) => appendJsonlRecord(options.filePath, 1, record)
7261
+ });
7262
+ }
7263
+ function validateDeliverySequence(previous, record, _lineNumber) {
7264
+ if (previous === void 0) return record.revision === 1 && record.state === "pending" ? void 0 : "first record";
7265
+ if (record.revision !== previous.revision + 1) return "revision";
7266
+ return previous.state === "pending" && record.state === "delivered" ? void 0 : "transition";
7267
+ }
7268
+ function isDeliveryRecord(value) {
7269
+ if (!isRecord$4(value)) return false;
7270
+ if (typeof value.deliveryId !== "string" || value.deliveryId.trim() === "" || typeof value.sessionId !== "string" || value.sessionId.trim() === "") return false;
7271
+ if (typeof value.kind !== "string" || ![
7272
+ "progress",
7273
+ "final",
7274
+ "stopped",
7275
+ "failed",
7276
+ "reconciliation"
7277
+ ].includes(value.kind)) return false;
7278
+ if (typeof value.text !== "string") return false;
7279
+ if (value.state !== "pending" && value.state !== "delivered") return false;
7280
+ if (!Number.isInteger(value.revision) || value.revision < 1) return false;
7281
+ if (value.providerMessageId !== void 0 && typeof value.providerMessageId !== "string") return false;
7282
+ return typeof value.createdAt === "string" && Number.isFinite(Date.parse(value.createdAt));
7283
+ }
7284
+ //#endregion
7285
+ //#region src/infrastructure/feishu/feishu-interactive-card-send.ts
7286
+ function sendFeishuInteractiveCard(client, request, errorMessage) {
7287
+ return Effect.gen(function* () {
7288
+ const response = yield* client.request(request, errorMessage);
7289
+ const providerMessageId = readMessageId(response.body.data);
7290
+ if (!providerMessageId) return yield* Effect.fail(new FeishuOpenApiError(response.status, response.body.code, "Feishu send response missing message_id"));
7291
+ return Object.freeze({ providerMessageId });
7292
+ });
7293
+ }
7294
+ function readMessageId(data) {
7295
+ if (data === null || typeof data !== "object" || Array.isArray(data)) return void 0;
7296
+ const value = data.message_id;
7297
+ return typeof value === "string" && value.trim() !== "" ? value : void 0;
7298
+ }
7299
+ //#endregion
7300
+ //#region src/infrastructure/feishu/feishu-background-session-delivery.ts
7301
+ function createConfiguredFeishuBackgroundSessionDelivery(options) {
7302
+ const baseUrl = options.config.feishu.baseUrl.replace(/\/$/, "");
7303
+ return { deliver: (input) => Effect.runPromise(sendFeishuInteractiveCard(options.client, {
7304
+ body: {
7305
+ content: JSON.stringify(createBackgroundSessionCard(input)),
7306
+ msg_type: "interactive",
7307
+ receive_id: input.chatId,
7308
+ uuid: deliveryUuid$1(input.deliveryId)
7309
+ },
7310
+ method: "POST",
7311
+ url: `${baseUrl}/open-apis/im/v1/messages?receive_id_type=chat_id`
7312
+ }, "Feishu background session delivery failed")) };
7313
+ }
7314
+ function createBackgroundSessionCard(input) {
7315
+ const title = cardTitle(input.kind);
7316
+ const headerTemplate = cardHeaderTemplate(input.kind);
7317
+ return {
7318
+ body: { elements: [
7319
+ {
7320
+ content: `**${escapeCardText(title)}** · ${escapeCardText(input.displayName)}`,
7321
+ tag: "markdown"
7322
+ },
7323
+ {
7324
+ content: input.text,
7325
+ tag: "markdown"
7326
+ },
7327
+ {
7328
+ content: `会话 ${escapeCardText(input.sessionId)} · _由 Rivus 后台会话生成_`,
7329
+ tag: "markdown"
7330
+ }
7331
+ ] },
7332
+ config: { update_multi: true },
7333
+ header: {
7334
+ template: headerTemplate,
7335
+ title: {
7336
+ content: title,
7337
+ tag: "plain_text"
7338
+ }
7339
+ },
7340
+ schema: "2.0"
7341
+ };
7342
+ }
7343
+ function cardTitle(kind) {
7344
+ switch (kind) {
7345
+ case "progress": return "后台会话进展";
7346
+ case "final": return "后台会话完成";
7347
+ case "failed": return "后台会话失败";
7348
+ case "stopped": return "后台会话已停止";
7349
+ case "reconciliation": return "后台会话需对账";
7350
+ }
7351
+ }
7352
+ function cardHeaderTemplate(kind) {
7353
+ switch (kind) {
7354
+ case "final": return "green";
7355
+ case "failed":
7356
+ case "reconciliation": return "red";
7357
+ case "stopped": return "orange";
7358
+ case "progress": return "blue";
7359
+ }
7360
+ }
7361
+ function escapeCardText(value) {
7362
+ return value.replace(/([*_~`|<>])/g, "\\$1");
7363
+ }
7364
+ function deliveryUuid$1(deliveryId) {
7365
+ return `rivus-${createHash("sha256").update(`background-session:${deliveryId}`).digest("hex").slice(0, 32)}`;
7366
+ }
7367
+ //#endregion
5848
7368
  //#region src/application/memory/rivus-memory-tool.ts
5849
7369
  function createRivusMemoryTool(memory) {
5850
7370
  const handles = Array.isArray(memory) ? [...memory] : [memory];
@@ -7015,21 +8535,16 @@ function createConfiguredFeishuTextReplySender(options) {
7015
8535
  //#region src/infrastructure/feishu/feishu-automation-card-sender.ts
7016
8536
  function createConfiguredFeishuAutomationCardSender(options) {
7017
8537
  const baseUrl = options.config.feishu.baseUrl.replace(/\/$/, "");
7018
- return { send: (input) => Effect.gen(function* () {
7019
- const response = yield* options.client.request({
7020
- body: {
7021
- content: JSON.stringify(createAutomationCard(input.markdown)),
7022
- msg_type: "interactive",
7023
- receive_id: input.receiveId,
7024
- uuid: deliveryUuid(input.idempotencyKey)
7025
- },
7026
- method: "POST",
7027
- url: `${baseUrl}/open-apis/im/v1/messages?receive_id_type=${encodeURIComponent(input.receiveIdType)}`
7028
- }, "Feishu send failed");
7029
- const providerMessageId = readMessageId(response.body.data);
7030
- if (!providerMessageId) return yield* Effect.fail(new FeishuOpenApiError(response.status, response.body.code, "Feishu send response missing message_id"));
7031
- return Object.freeze({ providerMessageId });
7032
- }) };
8538
+ return { send: (input) => sendFeishuInteractiveCard(options.client, {
8539
+ body: {
8540
+ content: JSON.stringify(createAutomationCard(input.markdown)),
8541
+ msg_type: "interactive",
8542
+ receive_id: input.receiveId,
8543
+ uuid: deliveryUuid(input.idempotencyKey)
8544
+ },
8545
+ method: "POST",
8546
+ url: `${baseUrl}/open-apis/im/v1/messages?receive_id_type=${encodeURIComponent(input.receiveIdType)}`
8547
+ }, "Feishu send failed") };
7033
8548
  }
7034
8549
  function createAutomationCard(markdown) {
7035
8550
  const presentation = splitPresentation(markdown);
@@ -7068,11 +8583,6 @@ function splitPresentation(markdown) {
7068
8583
  function deliveryUuid(idempotencyKey) {
7069
8584
  return `rivus-${createHash("sha256").update(idempotencyKey).digest("hex").slice(0, 32)}`;
7070
8585
  }
7071
- function readMessageId(data) {
7072
- if (data === null || typeof data !== "object" || Array.isArray(data)) return void 0;
7073
- const value = data.message_id;
7074
- return typeof value === "string" && value.trim() !== "" ? value : void 0;
7075
- }
7076
8586
  //#endregion
7077
8587
  //#region src/application/interaction/human-interaction-repository.ts
7078
8588
  var HumanInteractionRepositoryError = class extends Error {
@@ -7426,4 +8936,4 @@ function validateDecisionInput(input) {
7426
8936
  if (input.recommendedOptionId && !optionIds.includes(input.recommendedOptionId)) throw new Error("recommended user decision option must be available");
7427
8937
  }
7428
8938
  //#endregion
7429
- export { AgentContextBudgetExceeded, AgentEventHandlerFailed, AgentEventLogStoreError, AgentEventSinkFailed, AgentHarnessBusy, AgentInstanceBusy, AgentInstanceConflict, AgentLoopFailed, AgentMemoryError, AgentRunCancelled, AgentRuntimeDisposed, AutomationMandateError, CardPresentationTransitionDenied, CompactionError, DEFAULT_CARD_STREAM_LEASE_MS, DelegationDenied, DeliveryOutboxError, FeishuCardPresentationNotFound, FeishuCardTargetNotFound, FeishuCardTargetRegistryStoreError, FeishuCotProtocolError, FeishuEndpointCredentialError, FeishuOpenApiError, FeishuTenantAccessTokenError, HumanInteractionRepositoryError, HumanInteractionTransitionDenied, InvalidFeishuCardAction, InvalidFeishuMessageContent, InvalidFeishuSessionReference, InvalidInvocationAuthority, InvalidProjectSkillCatalog, InvalidRecoveryAction, InvalidRivusEndpointBinding, InvalidRivusPlugin, InvalidRivusProjectSpace, InvalidStableJson, InvalidToolInput, InvalidWorkspaceRoot, LangfuseTelemetryConfigError, MEMORY_SCOPES, OpenClawEnvImportError, PluginStateConflict, RIVUS_MEMORY_TOOL_ID, RIVUS_MEMORY_TOOL_PLUGIN_ID, RIVUS_MEMORY_TOOL_VERSION, RIVUS_PLUGIN_API_VERSION, RivusDaemonConfigError, RivusDeploymentAutomationReadinessError, RivusDeploymentDaemonLifecycleError, RivusDeploymentManifestError, RivusDeploymentReadinessError, RivusPluginConformanceError, RivusPluginLoadError, RivusToolInputRejected, SessionSchedulerCapacityExceeded, SessionSchedulerDisposed, ToolInvocationDenied, UnsupportedFeishuCardAction, UnsupportedFeishuMessage, WorkspaceInstructionsSourceError, acceptsCardPresentationProgress, activeCardPresentation, assembleAgentContext, assertRivusPluginConforms, commitAutomationOutcome, createAgentCommandFromFeishuCardAction, createAgentCommandFromFeishuMessage, createAgentConversationMessages, createAgentDomainEventHandler, createAgentDomainEventSink, createAgentDomainEventSinkFromCallback, createAgentHarness, createAgentHarnessClient, createAgentHarnessPooledRuntime, createAgentInstanceRegistry, createAgentLoopFromCallback, createAgentLoopModelExecutionEnd, createAgentLoopModelExecutionStart, createAgentLoopPromptTransformer, createAgentLoopSkillExecutionEnd, createAgentLoopSkillExecutionStart, createAgentLoopTextDelta, createAgentLoopThinkingDelta, createAgentLoopToolExecutionEnd, createAgentLoopToolExecutionStart, createAgentLoopToolExecutionUpdate, createAgentMemoryService, createAgentRunUpdateHandler, createAgentRuntime, createAgentRuntimePool, createAgentTranscriptMessages, createAgentTranscriptTurn, createAgentsMdInstructionsProvider, createAsyncIterableAgentLoop, createAutomationMandateStore, createCoalescingFeishuPublisher, createCompactionService, createCompositeRivusDaemonTransport, createConfiguredFeishuAutomationCardSender, createConfiguredFeishuCardKitPublisher, createConfiguredFeishuCardKitTargetCreator, createConfiguredFeishuCardRolloverRuntime, createConfiguredFeishuHumanInteractionPresenter, createConfiguredFeishuOpenApiClient, createConfiguredFeishuTextReplySender, createConfiguredRivusDaemonBootstrap, createConfiguredRivusDeploymentDaemon, createDailyAutomationSchedule, createDefaultAgentHarness, createDefaultAgentHarnessClient, createDefaultAgentHarnessClientFromCallback, createDefaultAgentHarnessClientFromTextCallback, createDefaultAgentHarnessFromCallback, createDefaultAgentHarnessFromTextCallback, createDefaultAgentRuntime, createDefaultAgentRuntimeFromCallback, createDefaultAgentRuntimeFromTextCallback, createDelegationService, createDeliveryOutbox, createEventAgentLoop, createFakeRivusPlugin, createFeishuAgentDaemon, createFeishuAgentRunCard, createFeishuAgentRuntime, createFeishuCardActionCallbackResponse, createFeishuCardActionErrorResponse, createFeishuCardDeliveryLedger, createFeishuCardDeliveryReconciler, createFeishuCardKitOpenApiClient, createFeishuCardKitOpenApiTargetCreator, createFeishuCardKitPublisher, createFeishuCardPresentationStore, createFeishuCardRollover, createFeishuCardRolloverSupervisor, createFeishuCardTargetPreparation, createFeishuConversationId, createFeishuCotPublisher, createFeishuDeploymentEndpoint, createFeishuEventHandlers, createFeishuHumanInteractionCard, createFeishuHumanInteractionPresenter, createFeishuInboxRepository, createFeishuMessageQueue, createFeishuMessageWorker, createFeishuOpenApiClient, createFeishuPeriodicFlush, createFeishuPresentationPreparation, createFeishuSessionKey, createFeishuStreamProjector, createFeishuTenantAccessTokenProvider, createFeishuWebSocketDaemon, createFeishuWorkerLoop, createFixedClock, createHumanInteractionEndpointRegistry, createHumanInteractionService, createHumanInteractionToolApprovalGateway, createInMemoryFeishuCardTargetRegistry, createInMemoryHumanInteractionRepository, createInvocationAuthority, createJsonFetchRequest, createJsonFileFeishuCardTargetRegistry, createJsonlAgentEventLog, createJsonlHumanInteractionRepository, createLangfuseAgentTelemetry, createLazyFeishuWebSocketEventDispatcher, createMemoryNamespace, createOpenTelemetryAgentEventSink, createOpenTelemetryAgentTelemetry, createPiAgentLoop, createPiSdkAgentLoop, createPiSessionRegistry, createPluginStateStore, createProjectMemoryPromptPreparer, createRateLimitedFeishuPublisher, createRecoveryAction, createRecoveryControl, createRivusAgentHost, createRivusDaemonProcess, createRivusDaemonShutdownController, createRivusDaemonStatusHttpServer, createRivusDaemonStatusReporter, createRivusDeploymentCliProcess, createRivusDeploymentDaemon, createRivusEnvFromOpenClawConfig, createRivusMemoryTool, createRivusMemoryToolContract, createRivusMemoryToolDescriptor, createRivusPluginCatalog, createRoutedHumanInteractionToolApprovalService, createScheduledAutomation, createSequenceRunIds, createSessionScheduler, createSubagentCoordinator, createSystemClock, createTelemetryContentRedactor, createTestClock, createTextAgentLoop, createTextAgentLoopFromCallback, createToolBroker, createToolInputDigest, createToolOperationLedger, createUuidRunIds, createWorkspaceRootHandle, describeFeishuMessageIntake, evolveAgentRun, formatRivusEnvFile, initialAgentRunState, intersectToolIds, isAgentToolExecutionEvent, isAssistantTextDeltaEvent, isAssistantThinkingDeltaEvent, isCardPresentationHandoffDue, isTerminalAgentDomainEvent, isTerminalAgentRunPhase, loadNodeRivusPluginModule, loadRivusDaemonConfig, loadRivusDeployment, loadRivusDeploymentManifest, normalizeStableJson, openJsonAutomationTickRepository, openJsonlAgentMemoryService, openJsonlFeishuCardDeliveryLedger, openJsonlFeishuInboxRepository, openJsonlRecoveryControl, openJsonlToolOperationLedger, replayAgentHistory, replayAgentTranscript, requiresToolApproval, resolveFeishuEndpointCredentials, resolveLangfuseTelemetryConfig, resolveRivusAgentDefinition, resolveRivusProjectSpace, restoreAgentHistory, restoreConfiguredRivusDaemonBootstrap, restrictMemoryScopesForAudience, runRivusDaemonCli, shouldAcceptFeishuEndpointMessage, transitionHumanInteraction, validateProjectSkillCatalog, validateProjectSkillCommand, validateRivusDeploymentManifest };
8939
+ export { AgentContextBudgetExceeded, AgentEventHandlerFailed, AgentEventLogStoreError, AgentEventSinkFailed, AgentHarnessBusy, AgentInstanceBusy, AgentInstanceConflict, AgentLoopFailed, AgentMemoryError, AgentRunCancelled, AgentRuntimeDisposed, AutomationMandateError, BACKGROUND_SESSION_DELIVERY_JSONL_VERSION, BACKGROUND_SESSION_JSONL_VERSION, BACKGROUND_SESSION_SESSION_KEY_PREFIX, BACKGROUND_SESSION_START_TOOL_ID, BACKGROUND_SESSION_TOOL_IDS, BACKGROUND_SESSION_TOOL_PLUGIN_ID, BACKGROUND_SESSION_TOOL_VERSION, BackgroundSessionCallerDenied, BackgroundSessionDeliveryConflict, BackgroundSessionRepositoryConflict, BackgroundSessionRepositoryCorrupted, BackgroundSessionTransitionDenied, CardPresentationTransitionDenied, CompactionError, DEFAULT_BACKGROUND_SESSION_LEASE_MS, DEFAULT_BACKGROUND_SESSION_LEASE_RENEWAL_INTERVAL_MS, DEFAULT_BACKGROUND_SESSION_LIFETIME_MS, DEFAULT_BACKGROUND_SESSION_MAX_CONCURRENT_SESSIONS, DEFAULT_BACKGROUND_SESSION_MAX_CONSECUTIVE_FAILURES, DEFAULT_BACKGROUND_SESSION_RETRY_BACKOFF_MS, DEFAULT_BACKGROUND_SESSION_STEP_TIMEOUT_MS, DEFAULT_BACKGROUND_SESSION_SUPERVISOR_INTERVAL_MS, DEFAULT_CARD_STREAM_LEASE_MS, DelegationDenied, DeliveryOutboxError, FeishuCardPresentationNotFound, FeishuCardTargetNotFound, FeishuCardTargetRegistryStoreError, FeishuCotProtocolError, FeishuEndpointCredentialError, FeishuOpenApiError, FeishuTenantAccessTokenError, HumanInteractionRepositoryError, HumanInteractionTransitionDenied, InvalidFeishuCardAction, InvalidFeishuMessageContent, InvalidFeishuSessionReference, InvalidInvocationAuthority, InvalidProjectSkillCatalog, InvalidRecoveryAction, InvalidRivusEndpointBinding, InvalidRivusPlugin, InvalidRivusProjectSpace, InvalidStableJson, InvalidToolInput, InvalidWorkspaceRoot, LangfuseTelemetryConfigError, MEMORY_SCOPES, OpenClawEnvImportError, PluginStateConflict, RIVUS_MEMORY_TOOL_ID, RIVUS_MEMORY_TOOL_PLUGIN_ID, RIVUS_MEMORY_TOOL_VERSION, RIVUS_PLUGIN_API_VERSION, RivusDaemonConfigError, RivusDeploymentAutomationReadinessError, RivusDeploymentDaemonLifecycleError, RivusDeploymentManifestError, RivusDeploymentReadinessError, RivusPluginConformanceError, RivusPluginLoadError, RivusToolInputRejected, SessionSchedulerCapacityExceeded, SessionSchedulerDisposed, ToolInvocationDenied, UnsupportedFeishuCardAction, UnsupportedFeishuMessage, WorkspaceInstructionsSourceError, acceptsCardPresentationProgress, activeCardPresentation, appendBackgroundSessionInput, assembleAgentContext, assertRivusPluginConforms, backgroundSessionToolIds, claimBackgroundSession, commitAutomationOutcome, completeBackgroundSessionStep, completeBackgroundSessionStop, createAgentCommandFromFeishuCardAction, createAgentCommandFromFeishuMessage, createAgentConversationMessages, createAgentDomainEventHandler, createAgentDomainEventSink, createAgentDomainEventSinkFromCallback, createAgentHarness, createAgentHarnessClient, createAgentHarnessPooledRuntime, createAgentInstanceRegistry, createAgentLoopFromCallback, createAgentLoopModelExecutionEnd, createAgentLoopModelExecutionStart, createAgentLoopPromptTransformer, createAgentLoopSkillExecutionEnd, createAgentLoopSkillExecutionStart, createAgentLoopTextDelta, createAgentLoopThinkingDelta, createAgentLoopToolExecutionEnd, createAgentLoopToolExecutionStart, createAgentLoopToolExecutionUpdate, createAgentMemoryService, createAgentRunUpdateHandler, createAgentRuntime, createAgentRuntimePool, createAgentTranscriptMessages, createAgentTranscriptTurn, createAgentsMdInstructionsProvider, createAsyncIterableAgentLoop, createAutomationMandateStore, createBackgroundSession, createBackgroundSessionCard, createBackgroundSessionDeliveryStore, createBackgroundSessionHostTools, createBackgroundSessionKey, createBackgroundSessionRepository, createBackgroundSessionService, createBackgroundSessionStepSourceMessageId, createBackgroundSessionSupervisor, createBackgroundSessionToolContracts, createCoalescingFeishuPublisher, createCompactionService, createCompositeRivusDaemonTransport, createConfiguredFeishuAutomationCardSender, createConfiguredFeishuBackgroundSessionDelivery, createConfiguredFeishuCardKitPublisher, createConfiguredFeishuCardKitTargetCreator, createConfiguredFeishuCardRolloverRuntime, createConfiguredFeishuHumanInteractionPresenter, createConfiguredFeishuOpenApiClient, createConfiguredFeishuTextReplySender, createConfiguredRivusDaemonBootstrap, createConfiguredRivusDeploymentDaemon, createDailyAutomationSchedule, createDefaultAgentHarness, createDefaultAgentHarnessClient, createDefaultAgentHarnessClientFromCallback, createDefaultAgentHarnessClientFromTextCallback, createDefaultAgentHarnessFromCallback, createDefaultAgentHarnessFromTextCallback, createDefaultAgentRuntime, createDefaultAgentRuntimeFromCallback, createDefaultAgentRuntimeFromTextCallback, createDelegationService, createDeliveryOutbox, createEventAgentLoop, createFakeRivusPlugin, createFeishuAgentDaemon, createFeishuAgentRunCard, createFeishuAgentRuntime, createFeishuCardActionCallbackResponse, createFeishuCardActionErrorResponse, createFeishuCardDeliveryLedger, createFeishuCardDeliveryReconciler, createFeishuCardKitOpenApiClient, createFeishuCardKitOpenApiTargetCreator, createFeishuCardKitPublisher, createFeishuCardPresentationStore, createFeishuCardRollover, createFeishuCardRolloverSupervisor, createFeishuCardTargetPreparation, createFeishuConversationId, createFeishuCotPublisher, createFeishuDeploymentEndpoint, createFeishuEventHandlers, createFeishuHumanInteractionCard, createFeishuHumanInteractionPresenter, createFeishuInboxRepository, createFeishuMessageQueue, createFeishuMessageWorker, createFeishuOpenApiClient, createFeishuPeriodicFlush, createFeishuPresentationPreparation, createFeishuSessionKey, createFeishuStreamProjector, createFeishuTenantAccessTokenProvider, createFeishuWebSocketDaemon, createFeishuWorkerLoop, createFixedClock, createHumanInteractionEndpointRegistry, createHumanInteractionService, createHumanInteractionToolApprovalGateway, createInMemoryFeishuCardTargetRegistry, createInMemoryHumanInteractionRepository, createInvocationAuthority, createJsonFetchRequest, createJsonFileFeishuCardTargetRegistry, createJsonlAgentEventLog, createJsonlHumanInteractionRepository, createLangfuseAgentTelemetry, createLazyFeishuWebSocketEventDispatcher, createMemoryNamespace, createOpenTelemetryAgentEventSink, createOpenTelemetryAgentTelemetry, createPiAgentLoop, createPiSdkAgentLoop, createPiSessionRegistry, createPluginStateStore, createProjectMemoryPromptPreparer, createRateLimitedFeishuPublisher, createRecoveryAction, createRecoveryControl, createRivusAgentHost, createRivusDaemonProcess, createRivusDaemonShutdownController, createRivusDaemonStatusHttpServer, createRivusDaemonStatusReporter, createRivusDeploymentCliProcess, createRivusDeploymentDaemon, createRivusEnvFromOpenClawConfig, createRivusMemoryTool, createRivusMemoryToolContract, createRivusMemoryToolDescriptor, createRivusPluginCatalog, createRoutedHumanInteractionToolApprovalService, createScheduledAutomation, createSequenceRunIds, createSessionScheduler, createSubagentCoordinator, createSystemClock, createTelemetryContentRedactor, createTestClock, createTextAgentLoop, createTextAgentLoopFromCallback, createToolBroker, createToolInputDigest, createToolOperationLedger, createUuidRunIds, createWorkspaceRootHandle, describeFeishuMessageIntake, evolveAgentRun, extendBackgroundSessionDefinition, failBackgroundSessionStep, formatRivusEnvFile, initialAgentRunState, intersectToolIds, isAgentToolExecutionEvent, isAssistantTextDeltaEvent, isAssistantThinkingDeltaEvent, isBackgroundSessionDue, isBackgroundSessionLeaseExpired, isBackgroundSessionState, isBackgroundSessionTerminalPhase, isBackgroundSessionToolId, isCardPresentationHandoffDue, isTerminalAgentDomainEvent, isTerminalAgentRunPhase, loadNodeRivusPluginModule, loadRivusDaemonConfig, loadRivusDeployment, loadRivusDeploymentManifest, narrowBackgroundSessionDefinition, normalizeStableJson, openJsonAutomationTickRepository, openJsonlAgentMemoryService, openJsonlBackgroundSessionDeliveryStore, openJsonlBackgroundSessionRepository, openJsonlFeishuCardDeliveryLedger, openJsonlFeishuInboxRepository, openJsonlRecoveryControl, openJsonlToolOperationLedger, parkBackgroundSessionForReconciliation, progressDeliveryId, releaseBackgroundSessionLease, renewBackgroundSessionLease, replayAgentHistory, replayAgentTranscript, requestBackgroundSessionStop, requeueInterruptedBackgroundSessionStep, requiresToolApproval, resolveBackgroundSessionReconciliation, resolveBackgroundSessionSupervisorIntervalMs, resolveFeishuEndpointCredentials, resolveLangfuseTelemetryConfig, resolveRivusAgentDefinition, resolveRivusProjectSpace, restoreAgentHistory, restoreConfiguredRivusDaemonBootstrap, restrictMemoryScopesForAudience, runRivusDaemonCli, sessionIdFromSessionKey, shouldAcceptFeishuEndpointMessage, suspendBackgroundSession, terminalDeliveryId, transitionHumanInteraction, validateProjectSkillCatalog, validateProjectSkillCommand, validateRivusDeploymentManifest };