@rivus/gateway 0.17.0 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,11 @@
1
1
  import { S as deepFreeze$1, _ as loadMergedLocalEnvFile, a as createRivusModelManagementSocketServer, d as gatewayPackageDirectory, h as loadRivusDeploymentManifest, i as RIVUS_RELEASE_DESCRIPTOR, n as disposeRuntimeCacheEntries, o as createRivusModelManagementFailure, p as gatewaySkillsDirectory, r as invokeRuntimeControl, t as createRuntimeCache, v as resolveFeishuEndpointCredentials } from "../chunks/runtime-cache.js";
2
2
  import { t as createBackgroundSessionToolContracts } from "../chunks/background-session-authority.js";
3
+ import { a as openTerminalBindingsStore, c as classifyFeishuCardOwnership, d as parseFeishuTerminalMessage, i as openTerminalMessageClaims, l as createFeishuTerminalDelivery, n as embeddedHerdrRouteSchema, o as terminalAgentSchema, s as createHerdrSocketClient, t as createHerdrFeishuBridgeRuntime, u as parseFeishuTerminalAction } from "../chunks/herdr-feishu-runtime.js";
3
4
  import { createRandomId, createSha256Digest, createStableId } from "@rivus/platform/identity";
4
5
  import { ATTACHMENT_RECOVERY_COMMAND, AgentEventHandlerFailed, AgentEventSinkFailed, AgentLoopFailed, AgentRunCancelled, ModelChangeBoundaryDeadlineError, RivusToolInputRejected, ToolExecutorInputRejected, ToolInvocationDenied, createAgentHarness, createAgentsMdInstructionsSource, createInvocationAuthority, createJsonlAgentEventLog, createLangfuseAgentTelemetry, createMemoryService, createProjectMemoryPromptPreparer, createRecoveryAction, createRivusMemoryToolDescriptor, createRunAdmission, createTelemetryContentRedactor, createToolBroker, createToolInputDigest, createWorkspaceInstructionsResolver, createWorkspaceRootHandle, normalizeAgentInvocation, openJsonlAgentMemoryRepository, openJsonlToolOperationLedger, remainingUntil, resolveInvocationAuthority, resolveLangfuseTelemetryConfig, restoreAgentHistory } from "@rivus/runtime";
5
6
  import { homedir, tmpdir } from "node:os";
6
7
  import { delimiter, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
7
- import { Cause, Deferred, Effect, Either, Exit, Fiber } from "effect";
8
+ import { Cause, Deferred, Effect, Exit, Fiber, Result, Schema, Semaphore } from "effect";
8
9
  import { access, appendFile, chmod, constants, lstat, mkdir, open, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
9
10
  import { createHash, randomUUID } from "node:crypto";
10
11
  import { appendJsonlRecord, isRecord, loadJsonlSnapshotStore, readPersistenceFile, writeAtomicTextFile, writePersistenceFile } from "@rivus/platform/persistence";
@@ -976,7 +977,7 @@ var ModelStateRepositoryConflict = class extends Error {
976
977
  };
977
978
  function createInMemoryModelStateRepository(options) {
978
979
  let current = cloneAndValidate$1(options.initial);
979
- const serial = Effect.unsafeMakeSemaphore(1);
980
+ const serial = Semaphore.makeUnsafe(1);
980
981
  return {
981
982
  load: () => Effect.sync(() => structuredClone(current)),
982
983
  persist: (input) => serial.withPermits(1)(Effect.uninterruptible(Effect.gen(function* () {
@@ -1071,9 +1072,9 @@ function createModelChangeRunBoundary(options) {
1071
1072
  admission.fence();
1072
1073
  const remaining = remainingUntil(options.clock, input.deadlineAt);
1073
1074
  if (remaining <= 0) return yield* Effect.fail(new ModelChangeBoundaryDeadlineError("Model change boundary deadline has passed"));
1074
- yield* admission.whenDrained().pipe(Effect.timeoutFail({
1075
+ yield* admission.whenDrained().pipe(Effect.timeoutOrElse({
1075
1076
  duration: Math.min(remaining, 2147483647),
1076
- onTimeout: () => new ModelChangeBoundaryDeadlineError("Active Runs did not drain before the model change deadline")
1077
+ orElse: () => Effect.fail(new ModelChangeBoundaryDeadlineError("Active Runs did not drain before the model change deadline"))
1077
1078
  }));
1078
1079
  return {
1079
1080
  activeRuns: 0,
@@ -1099,7 +1100,7 @@ const TOOL_VERSION = "1";
1099
1100
  /** Reuses Broker policy, exact approval binding and its durable operation ledger. */
1100
1101
  function createModelChangeAuthorization(options) {
1101
1102
  if (typeof options.managementAgentId !== "string" || options.managementAgentId.trim() === "") throw new Error("managementAgentId must be a non-empty string");
1102
- const serial = Effect.unsafeMakeSemaphore(1);
1103
+ const serial = Semaphore.makeUnsafe(1);
1103
1104
  const digest = (value) => createToolInputDigest(value, options.digest);
1104
1105
  const admissions = /* @__PURE__ */ new Map();
1105
1106
  const broker = createToolBroker({
@@ -1666,46 +1667,46 @@ function executeModelChangePending(options, initialState, initialPending) {
1666
1667
  phase: "waiting-for-boundary"
1667
1668
  });
1668
1669
  boundaryHeld = true;
1669
- const boundary = yield* Effect.either(options.boundary.waitForSafeBoundary({
1670
+ const boundary = yield* Effect.result(options.boundary.waitForSafeBoundary({
1670
1671
  deadlineAt: pending.budget.deadlineAt,
1671
1672
  requestId: pending.request.requestId
1672
1673
  }));
1673
- if (Either.isLeft(boundary)) return yield* saveFailed(modelErrorFrom(boundary.left, "boundary"));
1674
- const authorization = yield* Effect.either(revalidateAcceptedBinding());
1675
- if (Either.isLeft(authorization)) return yield* saveFailed(modelErrorFrom(authorization.left, "boundary"));
1676
- if (authorization.right.status === "rejected") return yield* saveFailed(modelError(authorization.right.code, authorization.right.reason, "boundary"));
1674
+ if (Result.isFailure(boundary)) return yield* saveFailed(modelErrorFrom(boundary.failure, "boundary"));
1675
+ const authorization = yield* Effect.result(revalidateAcceptedBinding());
1676
+ if (Result.isFailure(authorization)) return yield* saveFailed(modelErrorFrom(authorization.failure, "boundary"));
1677
+ if (authorization.success.status === "rejected") return yield* saveFailed(modelError(authorization.success.code, authorization.success.reason, "boundary"));
1677
1678
  yield* savePending({
1678
1679
  ...pending,
1679
1680
  phase: "validating"
1680
1681
  });
1681
1682
  const context = runtimeContext(state, pending);
1682
- const drained = yield* Effect.either(options.runtime.drain({
1683
+ const drained = yield* Effect.result(options.runtime.drain({
1683
1684
  ...context,
1684
1685
  deadlineAt: pending.budget.deadlineAt
1685
1686
  }));
1686
- if (Either.isLeft(drained)) return yield* restoreAfterFailure({
1687
+ if (Result.isFailure(drained)) return yield* restoreAfterFailure({
1687
1688
  context,
1688
- error: modelErrorFrom(drained.left, "activation"),
1689
+ error: modelErrorFrom(drained.failure, "activation"),
1689
1690
  options,
1690
1691
  saveFinal,
1691
1692
  savePending,
1692
1693
  state: () => state,
1693
1694
  pending: () => pending
1694
1695
  });
1695
- const validated = yield* Effect.either(options.runtime.validate({
1696
+ const validated = yield* Effect.result(options.runtime.validate({
1696
1697
  context,
1697
1698
  budget: executionBudget
1698
1699
  }));
1699
- if (Either.isLeft(validated)) return yield* restoreAfterFailure({
1700
+ if (Result.isFailure(validated)) return yield* restoreAfterFailure({
1700
1701
  context,
1701
- error: modelErrorFrom(validated.left, "validation"),
1702
+ error: modelErrorFrom(validated.failure, "validation"),
1702
1703
  options,
1703
1704
  saveFinal,
1704
1705
  savePending,
1705
1706
  state: () => state,
1706
1707
  pending: () => pending
1707
1708
  });
1708
- if (!validated.right.passed || !sameTarget(validated.right.model, target)) return yield* restoreAfterFailure({
1709
+ if (!validated.success.passed || !sameTarget(validated.success.model, target)) return yield* restoreAfterFailure({
1709
1710
  context,
1710
1711
  error: modelError("validation_failed", "Model validation did not prove the requested target", "validation"),
1711
1712
  options,
@@ -1714,25 +1715,25 @@ function executeModelChangePending(options, initialState, initialPending) {
1714
1715
  state: () => state,
1715
1716
  pending: () => pending
1716
1717
  });
1717
- const activationAuthorization = yield* Effect.either(revalidateAcceptedBinding());
1718
- if (Either.isLeft(activationAuthorization)) return yield* saveFailed(modelErrorFrom(activationAuthorization.left, "activation"));
1719
- if (activationAuthorization.right.status === "rejected") return yield* saveFailed(modelError(activationAuthorization.right.code, activationAuthorization.right.reason, "activation"));
1718
+ const activationAuthorization = yield* Effect.result(revalidateAcceptedBinding());
1719
+ if (Result.isFailure(activationAuthorization)) return yield* saveFailed(modelErrorFrom(activationAuthorization.failure, "activation"));
1720
+ if (activationAuthorization.success.status === "rejected") return yield* saveFailed(modelError(activationAuthorization.success.code, activationAuthorization.success.reason, "activation"));
1720
1721
  yield* savePending({
1721
1722
  ...pending,
1722
1723
  phase: "activating"
1723
1724
  });
1724
- const activated = yield* Effect.either(options.runtime.activate({
1725
+ const activated = yield* Effect.result(options.runtime.activate({
1725
1726
  beforeApply: () => Effect.gen(function* () {
1726
1727
  const guard = yield* revalidateAcceptedBinding();
1727
1728
  if (guard.status === "rejected") return yield* Effect.fail(new Error(guard.reason));
1728
1729
  }),
1729
1730
  context,
1730
- validation: validated.right,
1731
+ validation: validated.success,
1731
1732
  budget: executionBudget
1732
1733
  }));
1733
- if (Either.isLeft(activated)) return yield* restoreAfterFailure({
1734
+ if (Result.isFailure(activated)) return yield* restoreAfterFailure({
1734
1735
  context,
1735
- error: modelErrorFrom(activated.left, "activation"),
1736
+ error: modelErrorFrom(activated.failure, "activation"),
1736
1737
  options,
1737
1738
  saveFinal,
1738
1739
  savePending,
@@ -1755,7 +1756,7 @@ function executeModelChangePending(options, initialState, initialPending) {
1755
1756
  const now = requireNow(options);
1756
1757
  const applied = {
1757
1758
  ...pendingReceipt(state, pending, now),
1758
- current: validated.right.model,
1759
+ current: validated.success.model,
1759
1760
  phase: options.notification ? "notifying" : "completed",
1760
1761
  previous: state.current,
1761
1762
  revision: state.revision + 1,
@@ -1768,7 +1769,7 @@ function executeModelChangePending(options, initialState, initialPending) {
1768
1769
  const { pending: _pending, recoveryRequired: _recoveryRequired, ...stateWithoutTransient } = state;
1769
1770
  const appliedState = {
1770
1771
  ...stateWithoutTransient,
1771
- current: validated.right.model,
1772
+ current: validated.success.model,
1772
1773
  previous: state.current,
1773
1774
  requests: {
1774
1775
  ...state.requests,
@@ -1777,28 +1778,28 @@ function executeModelChangePending(options, initialState, initialPending) {
1777
1778
  revision: state.revision + 1,
1778
1779
  updatedAt: now
1779
1780
  };
1780
- const committed = yield* Effect.either(persistState(options, appliedState));
1781
- if (Either.isLeft(committed)) return yield* restoreAfterFailure({
1781
+ const committed = yield* Effect.result(persistState(options, appliedState));
1782
+ if (Result.isFailure(committed)) return yield* restoreAfterFailure({
1782
1783
  context,
1783
- error: modelErrorFrom(committed.left, "commit"),
1784
+ error: modelErrorFrom(committed.failure, "commit"),
1784
1785
  options,
1785
1786
  saveFinal,
1786
1787
  savePending,
1787
1788
  state: () => state,
1788
1789
  pending: () => pending
1789
1790
  });
1790
- state = committed.right;
1791
+ state = committed.success;
1791
1792
  pending = {
1792
1793
  ...pending,
1793
1794
  phase: "completed",
1794
1795
  updatedAt: now
1795
1796
  };
1796
- const release = yield* Effect.either(options.runtime.releasePrevious({
1797
+ const release = yield* Effect.result(options.runtime.releasePrevious({
1797
1798
  ...context,
1798
1799
  deadlineAt: pending.budget.deadlineAt
1799
1800
  }));
1800
- if (Either.isLeft(release)) {
1801
- const releaseError = modelErrorFrom(release.left, "activation");
1801
+ if (Result.isFailure(release)) {
1802
+ const releaseError = modelErrorFrom(release.failure, "activation");
1802
1803
  const withReleaseError = {
1803
1804
  ...applied,
1804
1805
  error: releaseError,
@@ -1834,16 +1835,16 @@ function restoreAfterFailure(input) {
1834
1835
  const { unknownPaidCallId: _unknownPaidCallId, ...withoutUnknown } = marked;
1835
1836
  yield* input.savePending({ ...withoutUnknown });
1836
1837
  }
1837
- const restored = yield* Effect.either(input.options.runtime.restore({
1838
+ const restored = yield* Effect.result(input.options.runtime.restore({
1838
1839
  budget: executionBudgetForRecovery(input),
1839
1840
  context: input.context,
1840
1841
  reason: input.error.message
1841
1842
  }));
1842
- if (Either.isRight(restored) && !input.pending().unknownPaidCallId && Object.keys(input.pending().budget.reservations).length === 0) {
1843
+ if (Result.isSuccess(restored) && !input.pending().unknownPaidCallId && Object.keys(input.pending().budget.reservations).length === 0) {
1843
1844
  const receipt = restoredReceipt(input.state(), input.pending(), input.error, requireNow(input.options));
1844
1845
  return yield* input.saveFinal(receipt);
1845
1846
  }
1846
- const recoveryError = Either.isLeft(restored) ? modelErrorFrom(restored.left, "restore") : modelError("recovery_unknown", "Model restore outcome is unknown", "restore", "unknown");
1847
+ const recoveryError = Result.isFailure(restored) ? modelErrorFrom(restored.failure, "restore") : modelError("recovery_unknown", "Model restore outcome is unknown", "restore", "unknown");
1847
1848
  const receipt = recoveryReceipt(input.state(), input.pending(), recoveryError, requireNow(input.options));
1848
1849
  return yield* input.saveFinal(receipt, true);
1849
1850
  });
@@ -1950,7 +1951,7 @@ function recoverModelChangePending(options, initialState, initialPending) {
1950
1951
  updatedAt: requireNow(options)
1951
1952
  };
1952
1953
  state = yield* persistState(options, stateWithPending(state, pending, pendingReceipt(state, pending, pending.updatedAt)));
1953
- const restored = yield* Effect.either(options.runtime.restore({
1954
+ const restored = yield* Effect.result(options.runtime.restore({
1954
1955
  budget: executionBudgetForRecovery({
1955
1956
  options,
1956
1957
  pending: () => pending,
@@ -1964,11 +1965,11 @@ function recoverModelChangePending(options, initialState, initialPending) {
1964
1965
  context,
1965
1966
  reason: "recovering an interrupted Model change"
1966
1967
  }));
1967
- if (Either.isRight(restored) && !pending.unknownPaidCallId && Object.keys(pending.budget.reservations).length === 0) {
1968
+ if (Result.isSuccess(restored) && !pending.unknownPaidCallId && Object.keys(pending.budget.reservations).length === 0) {
1968
1969
  const receipt = restoredReceipt(state, pending, void 0, requireNow(options));
1969
1970
  return yield* persistRecoveryReceipt(options, state, receipt);
1970
1971
  }
1971
- const error = Either.isLeft(restored) ? modelErrorFrom(restored.left, "restore") : modelError("recovery_unknown", "interrupted Model restore outcome is unknown", "restore", "unknown");
1972
+ const error = Result.isFailure(restored) ? modelErrorFrom(restored.failure, "restore") : modelError("recovery_unknown", "interrupted Model restore outcome is unknown", "restore", "unknown");
1972
1973
  const receipt = recoveryReceipt(state, pending, error, requireNow(options));
1973
1974
  return yield* persistRecoveryReceipt(options, state, receipt, error);
1974
1975
  });
@@ -2022,7 +2023,7 @@ function notifyModelChangeReceipt(options, requestId, retry, withModelSerial) {
2022
2023
  return Effect.gen(function* () {
2023
2024
  const attempted = yield* withModelSerial(beginNotificationAttempt(options, requestId));
2024
2025
  if (!attempted) return;
2025
- return yield* withModelSerial(finishNotificationAttempt(options, requestId, attempted, yield* Effect.either(notification.notify({
2026
+ return yield* withModelSerial(finishNotificationAttempt(options, requestId, attempted, yield* Effect.result(notification.notify({
2026
2027
  receipt: toPublicReceipt(attempted),
2027
2028
  retry
2028
2029
  }))));
@@ -2051,7 +2052,7 @@ function finishNotificationAttempt(options, requestId, attempted, delivered) {
2051
2052
  const latest = state.requests[requestId];
2052
2053
  if (!latest || !latest.notification || latest.notification.status === "sent") return latest;
2053
2054
  const updatedAt = requireNow(options);
2054
- return (yield* persistReceiptPatch(options, state, Either.isRight(delivered) ? {
2055
+ return (yield* persistReceiptPatch(options, state, Result.isSuccess(delivered) ? {
2055
2056
  ...latest,
2056
2057
  notification: {
2057
2058
  ...latest.notification,
@@ -2083,8 +2084,8 @@ function persistReceiptPatch(options, state, receipt) {
2083
2084
  //#endregion
2084
2085
  //#region src/core/application/deployment/model/model-change-coordinator.ts
2085
2086
  function createModelChangeCoordinator(options) {
2086
- const serial = Effect.unsafeMakeSemaphore(1);
2087
- const notificationSerial = Effect.unsafeMakeSemaphore(1);
2087
+ const serial = Semaphore.makeUnsafe(1);
2088
+ const notificationSerial = Semaphore.makeUnsafe(1);
2088
2089
  return {
2089
2090
  accept: (submission, transport) => serial.withPermits(1)(Effect.uninterruptible(acceptRequest(options, submission, transport))).pipe(Effect.flatMap((receipt) => notifyPendingReceipt(options, receipt, serial, notificationSerial))),
2090
2091
  recover: () => serial.withPermits(1)(Effect.uninterruptible(recoverModelChangeState(options))).pipe(Effect.flatMap(() => retryPendingNotifications(options, serial, notificationSerial))),
@@ -2099,7 +2100,7 @@ function createModelChangeCoordinator(options) {
2099
2100
  }),
2100
2101
  status: (requestId) => Effect.gen(function* () {
2101
2102
  const state = yield* loadState$1(options);
2102
- const actual = options.runtime.current ? yield* options.runtime.current().pipe(Effect.catchAll(() => Effect.succeed(void 0))) : void 0;
2103
+ const actual = options.runtime.current ? yield* options.runtime.current().pipe(Effect.catch(() => Effect.succeed(void 0))) : void 0;
2103
2104
  const receipt = requestId === void 0 ? void 0 : state.requests[requestId];
2104
2105
  if (requestId !== void 0 && !receipt) return yield* Effect.fail(new ModelChangeNotFound(`unknown Model change request: ${requestId}`));
2105
2106
  return toStatusView(state, actual, receipt);
@@ -2735,14 +2736,14 @@ function createBackgroundSessionRunStep(input) {
2735
2736
  })));
2736
2737
  return yield* Effect.raceFirst(run, waitForAbort$2(signal));
2737
2738
  }).pipe(Effect.onExit((exit) => Effect.suspend(() => {
2738
- cancellationRequested = signal.aborted || Exit.isInterrupted(exit);
2739
+ cancellationRequested = signal.aborted || Exit.hasInterrupts(exit);
2739
2740
  return cancellationRequested ? cancelAcceptedRun() : Effect.void;
2740
2741
  })));
2741
2742
  });
2742
2743
  };
2743
2744
  }
2744
2745
  function waitForAbort$2(signal) {
2745
- return Effect.async((resume) => {
2746
+ return Effect.callback((resume) => {
2746
2747
  const aborted = () => resume(Effect.fail(signal.reason ?? /* @__PURE__ */ new Error("background session step aborted")));
2747
2748
  if (signal.aborted) {
2748
2749
  aborted();
@@ -4494,7 +4495,7 @@ function createFeishuCardRollover(options) {
4494
4495
  const latestTextByRun = /* @__PURE__ */ new Map();
4495
4496
  const generationBaselineByRun = /* @__PURE__ */ new Map();
4496
4497
  const liveRuns = /* @__PURE__ */ new Map();
4497
- const semaphore = Effect.unsafeMakeSemaphore(1);
4498
+ const semaphore = Semaphore.makeUnsafe(1);
4498
4499
  const exclusive = (effect) => semaphore.withPermits(1)(effect);
4499
4500
  const exclusiveDeferred = (factory) => semaphore.withPermits(1)(Effect.suspend(factory));
4500
4501
  const record = (event, at) => {
@@ -4508,7 +4509,7 @@ function createFeishuCardRollover(options) {
4508
4509
  const failHandoffAndRecord = (input) => options.store.failHandoff({
4509
4510
  failedAt: input.failedAt,
4510
4511
  runId: input.runId
4511
- }).pipe(Effect.catchAll((storeError) => recordNow({
4512
+ }).pipe(Effect.catch((storeError) => recordNow({
4512
4513
  error: storeError,
4513
4514
  runId: input.runId,
4514
4515
  type: "presentation_write_failed"
@@ -4548,7 +4549,7 @@ function createFeishuCardRollover(options) {
4548
4549
  if (run && acceptsProgress) {
4549
4550
  const now = yield* options.clock.now;
4550
4551
  if (visibleChanged && isCardPresentationHandoffDue(chain, now.toISOString())) {
4551
- if (yield* options.publisher.flush(action.runId).pipe(Effect.map(() => true), Effect.catchAll((error) => recordNow({
4552
+ if (yield* options.publisher.flush(action.runId).pipe(Effect.map(() => true), Effect.catch((error) => recordNow({
4552
4553
  error,
4553
4554
  runId: action.runId,
4554
4555
  type: "presentation_write_failed"
@@ -4605,7 +4606,7 @@ function createFeishuCardRollover(options) {
4605
4606
  })), Effect.tap(() => options.store.markTerminal({
4606
4607
  runId: action.runId,
4607
4608
  terminalReceiptId: `${action.runId}:${action.type}`
4608
- }).pipe(Effect.catchAll((error) => recordNow({
4609
+ }).pipe(Effect.catch((error) => recordNow({
4609
4610
  error,
4610
4611
  runId: action.runId,
4611
4612
  type: "presentation_write_failed"
@@ -4643,7 +4644,7 @@ function createFeishuCardRollover(options) {
4643
4644
  const start = yield* options.store.beginHandoff({
4644
4645
  handoffAt: startedAt.toISOString(),
4645
4646
  runId
4646
- }).pipe(Effect.catchAll(() => Effect.succeed(void 0)));
4647
+ }).pipe(Effect.catch(() => Effect.succeed(void 0)));
4647
4648
  if (!start) return {
4648
4649
  reason: "not-streaming",
4649
4650
  status: "skipped"
@@ -4667,7 +4668,7 @@ function createFeishuCardRollover(options) {
4667
4668
  generation,
4668
4669
  presentationId: successorId,
4669
4670
  run
4670
- }).pipe(Effect.map((target) => ({ target })), Effect.catchAll((error) => failHandoffAndRecord({
4671
+ }).pipe(Effect.map((target) => ({ target })), Effect.catch((error) => failHandoffAndRecord({
4671
4672
  error,
4672
4673
  failedAt: startedAt.toISOString(),
4673
4674
  generation,
@@ -4689,7 +4690,7 @@ function createFeishuCardRollover(options) {
4689
4690
  leaseDeadlineAt: leaseDeadline(adoptedAt),
4690
4691
  presentationId: successorId
4691
4692
  }
4692
- }).pipe(Effect.map((presentation) => ({ presentation })), Effect.catchAll((error) => failHandoffAndRecord({
4693
+ }).pipe(Effect.map((presentation) => ({ presentation })), Effect.catch((error) => failHandoffAndRecord({
4693
4694
  error,
4694
4695
  failedAt: adoptedAt.toISOString(),
4695
4696
  generation,
@@ -4980,7 +4981,7 @@ function createCoalescingFeishuPublisher(options) {
4980
4981
  //#region src/adapters/feishu/card-presentation/streaming/feishu-rate-limited-publisher.ts
4981
4982
  function createRateLimitedFeishuPublisher(options) {
4982
4983
  let hasPublished = false;
4983
- const semaphore = Effect.unsafeMakeSemaphore(1);
4984
+ const semaphore = Semaphore.makeUnsafe(1);
4984
4985
  return { publish: (action) => semaphore.withPermits(1)(Effect.gen(function* () {
4985
4986
  if (hasPublished) yield* options.sleep(options.minIntervalMs);
4986
4987
  yield* options.publisher.publish(action);
@@ -5156,7 +5157,7 @@ function createFeishuCardKitOpenApiClient(options) {
5156
5157
  const updateTerminal = (request, status) => updateWholeCard(baseUrl, agentName, request, status, call, options.progressDisplay).pipe(Effect.tap(() => Effect.sync(() => streamingClosedCards.delete(request.cardId))));
5157
5158
  const publishStreamingUpdate = (cardId, update) => {
5158
5159
  if (streamingClosedCards.has(cardId)) return Effect.void;
5159
- return update.pipe(Effect.catchAll((error) => {
5160
+ return update.pipe(Effect.catch((error) => {
5160
5161
  if (!isStreamingClosed(error)) return Effect.fail(error);
5161
5162
  return Effect.sync(() => {
5162
5163
  streamingClosedCards.add(cardId);
@@ -5313,7 +5314,7 @@ function createCardDeliveryLedger(repository) {
5313
5314
  //#region src/adapters/outbound/persistence/card-delivery/in-memory-card-delivery-repository.ts
5314
5315
  function createInMemoryCardDeliveryRepository(options = {}) {
5315
5316
  const records = new Map(options.initial?.map((record) => [record.runId, cloneRecord(record)]) ?? []);
5316
- const semaphore = Effect.unsafeMakeSemaphore(1);
5317
+ const semaphore = Semaphore.makeUnsafe(1);
5317
5318
  return {
5318
5319
  get: (runId) => cloneOptional(records.get(runId)),
5319
5320
  transact: (runId, decide) => semaphore.withPermits(1)(Effect.uninterruptible(Effect.gen(function* () {
@@ -5812,7 +5813,7 @@ function createFeishuCardDeliveryReconciler(options) {
5812
5813
  let skipped = 0;
5813
5814
  for (const [runId, action] of terminalByRun) {
5814
5815
  if (options.ledger.isTerminalPublished(runId)) continue;
5815
- yield* options.publish(action).pipe(Effect.catchAll((error) => {
5816
+ yield* options.publish(action).pipe(Effect.catch((error) => {
5816
5817
  if (isMissingPresentationError(error)) {
5817
5818
  skipped += 1;
5818
5819
  return Effect.void;
@@ -6046,7 +6047,7 @@ function createConfiguredFeishuCardRolloverRuntime(options) {
6046
6047
  transport: {
6047
6048
  running: () => supervisor.running() && streaming.running(),
6048
6049
  start: async () => {
6049
- await Effect.runPromise(supervisor.recover().pipe(Effect.catchAll((error) => {
6050
+ await Effect.runPromise(supervisor.recover().pipe(Effect.catch((error) => {
6050
6051
  const reported = options.onError?.(error);
6051
6052
  return reported instanceof Promise ? Effect.promise(() => reported) : Effect.void;
6052
6053
  })));
@@ -6262,7 +6263,7 @@ function readResource(baseUrl, fetchImpl, getTenantAccessToken, input) {
6262
6263
  message: "Feishu tenant access token is unavailable"
6263
6264
  })));
6264
6265
  if (input.signal?.aborted === true) return yield* Effect.fail(cancelledError$1());
6265
- return yield* Effect.async((resume, effectSignal) => {
6266
+ return yield* Effect.callback((resume, effectSignal) => {
6266
6267
  const controller = new AbortController();
6267
6268
  const onAbort = () => controller.abort();
6268
6269
  const cleanupCaller = addAbortListener(input.signal, onAbort);
@@ -7481,14 +7482,14 @@ function readPages(readPage, input, limits) {
7481
7482
  ...pageToken === void 0 ? {} : { pageToken },
7482
7483
  ...input.signal ? { signal: input.signal } : {}
7483
7484
  };
7484
- const pageResult = yield* Effect.either(readPageWithSignal(readPage, request, input.signal));
7485
+ const pageResult = yield* Effect.result(readPageWithSignal(readPage, request, input.signal));
7485
7486
  pageCount += 1;
7486
- if (pageResult._tag === "Left") {
7487
+ if (pageResult._tag === "Failure") {
7487
7488
  if (input.signal !== void 0 && input.signal.aborted) return yield* Effect.fail(cancelledError());
7488
7489
  notices.push(contextNotice("unavailable", "无法读取引用消息的完整内容"));
7489
7490
  break;
7490
7491
  }
7491
- const parsed = parsePage(pageResult.right);
7492
+ const parsed = parsePage(pageResult.success);
7492
7493
  if (parsed === void 0) {
7493
7494
  notices.push(contextNotice("invalid_content", "引用消息接口返回结构无效"));
7494
7495
  break;
@@ -7530,7 +7531,7 @@ function readPageWithSignal(readPage, request, signal) {
7530
7531
  return signal === void 0 ? invoked : Effect.raceFirst(invoked, waitForAbort$1(signal));
7531
7532
  }
7532
7533
  function waitForAbort$1(signal) {
7533
- return Effect.async((resume) => {
7534
+ return Effect.callback((resume) => {
7534
7535
  const cancelled = Effect.fail(signal.reason ?? /* @__PURE__ */ new Error("Feishu message context was cancelled"));
7535
7536
  const onAbort = () => resume(cancelled);
7536
7537
  signal.addEventListener("abort", onAbort, { once: true });
@@ -8591,7 +8592,7 @@ function writeExclusiveReplacing(rootDirectory, path, bytes) {
8591
8592
  flag: "wx",
8592
8593
  mode: 384
8593
8594
  }));
8594
- return Effect.tryPromise(write).pipe(Effect.catchAll((error) => error.code === "EEXIST" ? Effect.tryPromise(async () => rm(await resolveSafeWorkspacePath(rootDirectory, path), { force: true })).pipe(Effect.flatMap(() => Effect.tryPromise(write))) : Effect.fail(error)));
8595
+ return Effect.tryPromise(write).pipe(Effect.catch((error) => error.code === "EEXIST" ? Effect.tryPromise(async () => rm(await resolveSafeWorkspacePath(rootDirectory, path), { force: true })).pipe(Effect.flatMap(() => Effect.tryPromise(write))) : Effect.fail(error)));
8595
8596
  }
8596
8597
  function verifyStoredPath(stored, rootDirectory, additionalRootDirectory) {
8597
8598
  return Effect.tryPromise(async () => {
@@ -8669,7 +8670,7 @@ function materializeRecoveryOutput(input) {
8669
8670
  outputPath: relativePath === "" ? outputPath : relativePath,
8670
8671
  sizeBytes: bytes.byteLength
8671
8672
  };
8672
- }).pipe(Effect.catchAll((error) => Effect.fail(attachConsumedBytes(error, input.output.sizeBytes))));
8673
+ }).pipe(Effect.catch((error) => Effect.fail(attachConsumedBytes(error, input.output.sizeBytes))));
8673
8674
  }
8674
8675
  /** The stored record of a recovery output; the caller states where its bytes are and what they measured. */
8675
8676
  function recoveredAttachment(grant, mimeType, path, size, digest) {
@@ -8903,13 +8904,13 @@ function createAttachmentAccess(input) {
8903
8904
  output,
8904
8905
  scope: input.scope,
8905
8906
  workspaceDirectory: input.workspaceDirectory
8906
- })), Effect.catchAll((error) => Effect.fail(attachConsumedBytes(error, output.sizeBytes))))), Effect.either, Effect.flatMap((result) => {
8907
+ })), Effect.catch((error) => Effect.fail(attachConsumedBytes(error, output.sizeBytes))))), Effect.result, Effect.flatMap((result) => {
8907
8908
  const activeTimeMs = Math.min(reservation.timeReservationMs, Math.max(0, Date.now() - startedAt));
8908
8909
  return grant.ledger.complete(reservation, {
8909
8910
  activeTimeMs,
8910
- bytes: result._tag === "Right" ? result.right.sizeBytes : consumedBytesOf(result.left, reservation.byteReservation),
8911
- status: result._tag === "Right" ? "stored" : signal.aborted ? "cancelled" : "failed"
8912
- }).pipe(Effect.flatMap(() => result._tag === "Right" ? Effect.succeed(result.right) : Effect.fail(result.left)));
8911
+ bytes: result._tag === "Success" ? result.success.sizeBytes : consumedBytesOf(result.failure, reservation.byteReservation),
8912
+ status: result._tag === "Success" ? "stored" : signal.aborted ? "cancelled" : "failed"
8913
+ }).pipe(Effect.flatMap(() => result._tag === "Success" ? Effect.succeed(result.success) : Effect.fail(result.failure)));
8913
8914
  }))));
8914
8915
  } : void 0;
8915
8916
  const readBytes = (attachmentId) => {
@@ -9080,14 +9081,14 @@ function downloadAndPublish(resource, request, reader, rootDirectory, signal, bu
9080
9081
  yield* writeExclusiveReplacing(rootDirectory, metadataTempPath, new TextEncoder().encode(JSON.stringify(metadata)));
9081
9082
  yield* Effect.tryPromise(async () => rename(await resolveSafeWorkspacePath(rootDirectory, metadataTempPath), await resolveSafeWorkspacePath(rootDirectory, metadataPath)));
9082
9083
  return stored;
9083
- }).pipe(Effect.either);
9084
- if (publication._tag === "Left") return yield* failPrepared(publication.left);
9084
+ }).pipe(Effect.result);
9085
+ if (publication._tag === "Failure") return yield* failPrepared(publication.failure);
9085
9086
  yield* ledger.complete(prepared.reservation, {
9086
9087
  activeTimeMs: activeTimeMs(),
9087
9088
  bytes: bytes.byteLength,
9088
9089
  status: "stored"
9089
9090
  });
9090
- return publication.right;
9091
+ return publication.success;
9091
9092
  }).pipe(Effect.ensuring(Effect.tryPromise(async () => rm(await resolveSafeWorkspacePath(rootDirectory, temporaryPath), { force: true })).pipe(Effect.ignore)));
9092
9093
  }
9093
9094
  function readWithRetries(reader, input, outerSignal, budget) {
@@ -9112,27 +9113,27 @@ function readWithRetries(reader, input, outerSignal, budget) {
9112
9113
  timeReservationMs: remainingMs
9113
9114
  });
9114
9115
  const attemptStarted = Date.now();
9115
- const result = yield* readAttempt(reader, input, outerSignal, Math.min(attemptTimeoutMs, reservation.timeReservationMs)).pipe(Effect.either);
9116
- if (result._tag === "Right") {
9116
+ const result = yield* readAttempt(reader, input, outerSignal, Math.min(attemptTimeoutMs, reservation.timeReservationMs)).pipe(Effect.result);
9117
+ if (result._tag === "Success") {
9117
9118
  const activeTimeMs = Math.min(reservation.timeReservationMs, Math.max(0, Date.now() - attemptStarted));
9118
- if (result.right.bytes.byteLength > maxBytes) {
9119
+ if (result.success.bytes.byteLength > maxBytes) {
9119
9120
  yield* ledger.complete(reservation, {
9120
9121
  activeTimeMs,
9121
- bytes: Math.min(result.right.bytes.byteLength, reservation.byteReservation),
9122
+ bytes: Math.min(result.success.bytes.byteLength, reservation.byteReservation),
9122
9123
  status: "failed"
9123
9124
  });
9124
9125
  return yield* Effect.fail(new FeishuAttachmentLimitExceeded("attachment byte limit exceeded"));
9125
9126
  }
9126
9127
  return {
9127
9128
  activeTimeMs,
9128
- read: result.right,
9129
+ read: result.success,
9129
9130
  reservation,
9130
9131
  startedAtMs: attemptStarted
9131
9132
  };
9132
9133
  }
9133
- lastError = result.left;
9134
+ lastError = result.failure;
9134
9135
  const delay = retryBackoffMs[attempt] ?? retryBackoffMs.at(-1) ?? 0;
9135
- const retry = isRetryableDownloadError(result.left) && attempt + 1 < maxAttempts;
9136
+ const retry = isRetryableDownloadError(result.failure) && attempt + 1 < maxAttempts;
9136
9137
  if (retry && delay > 0) yield* sleep(delay);
9137
9138
  if (outerSignal.aborted) {
9138
9139
  yield* ledger.cancel().pipe(Effect.ignore);
@@ -9140,8 +9141,8 @@ function readWithRetries(reader, input, outerSignal, budget) {
9140
9141
  }
9141
9142
  yield* ledger.complete(reservation, {
9142
9143
  activeTimeMs: Math.min(reservation.timeReservationMs, Math.max(0, Date.now() - attemptStarted)),
9143
- bytes: consumedBytesOf(result.left, reservation.byteReservation),
9144
- retryableFailure: isRetryableDownloadError(result.left),
9144
+ bytes: consumedBytesOf(result.failure, reservation.byteReservation),
9145
+ retryableFailure: isRetryableDownloadError(result.failure),
9145
9146
  status: "failed"
9146
9147
  });
9147
9148
  if (!retry) break;
@@ -9163,9 +9164,9 @@ function readAttempt(reader, input, outerSignal, timeoutMs) {
9163
9164
  const result = yield* reader.read({
9164
9165
  ...input,
9165
9166
  signal: controller.signal
9166
- }).pipe(Effect.either);
9167
- if (result._tag === "Left" && timedOut) return yield* Effect.fail(attachConsumedBytes(/* @__PURE__ */ new Error("attachment download timeout"), consumedBytesOf(result.left, input.maxBytes)));
9168
- return yield* result;
9167
+ }).pipe(Effect.result);
9168
+ if (result._tag === "Failure" && timedOut) return yield* Effect.fail(attachConsumedBytes(/* @__PURE__ */ new Error("attachment download timeout"), consumedBytesOf(result.failure, input.maxBytes)));
9169
+ return yield* Effect.fromResult(result);
9169
9170
  } finally {
9170
9171
  clearTimeout(timer);
9171
9172
  outerSignal.removeEventListener("abort", onAbort);
@@ -9377,13 +9378,13 @@ function createFeishuAttachmentPreparation(options) {
9377
9378
  retryBackoffMs,
9378
9379
  sleep,
9379
9380
  startedAt
9380
- }).pipe(Effect.either);
9381
- if (stored._tag === "Left") {
9381
+ }).pipe(Effect.result);
9382
+ if (stored._tag === "Failure") {
9382
9383
  if (signal.aborted) return yield* Effect.fail(new FeishuAttachmentPreparationCancelled());
9383
- destination.push(yield* failureNotice(resource, fallbackMessageId, classifyFailure(stored.left), outputPath, maxBytes));
9384
+ destination.push(yield* failureNotice(resource, fallbackMessageId, classifyFailure(stored.failure), outputPath, maxBytes));
9384
9385
  continue;
9385
9386
  }
9386
- accept(destination, attachmentId, resource, stored.right);
9387
+ accept(destination, attachmentId, resource, stored.success);
9387
9388
  }
9388
9389
  });
9389
9390
  yield* processParts(request.decoded.parts, parts, request.messageId);
@@ -9430,7 +9431,7 @@ function createFeishuAttachmentPreparation(options) {
9430
9431
  }),
9431
9432
  userInput
9432
9433
  };
9433
- }).pipe(Effect.onInterrupt(() => Effect.sync(() => controller.abort()).pipe(Effect.zipRight(ledger.cancel().pipe(Effect.ignore)))), Effect.ensuring(Effect.sync(() => {
9434
+ }).pipe(Effect.onInterrupt(() => Effect.sync(() => controller.abort()).pipe(Effect.andThen(ledger.cancel().pipe(Effect.ignore)))), Effect.ensuring(Effect.sync(() => {
9434
9435
  unregisterPreparation(request.sessionKey, controller);
9435
9436
  })));
9436
9437
  }
@@ -9796,7 +9797,7 @@ function effectForCandidate(operation) {
9796
9797
  return Effect.tryPromise({
9797
9798
  try: operation,
9798
9799
  catch: () => /* @__PURE__ */ new Error("attachment retention candidate inspection failed")
9799
- }).pipe(Effect.catchAll(() => Effect.succeed("error")));
9800
+ }).pipe(Effect.catch(() => Effect.succeed("error")));
9800
9801
  }
9801
9802
  function inspectPreparedCandidate(input) {
9802
9803
  return effectForCandidate(async () => {
@@ -10575,7 +10576,7 @@ function createFeishuCotPublisher(options) {
10575
10576
  url: `${baseUrl}/open-apis/im/v1/message_cot/complete/${encodeURIComponent(target.cotId)}?message_id=${encodeURIComponent(target.messageId)}&reason=${reason}`
10576
10577
  }, "Failed to complete Feishu COT message").pipe(Effect.timeout(COT_REQUEST_TIMEOUT), Effect.asVoid);
10577
10578
  const failTarget = (runId, target) => complete(target, "error").pipe(Effect.ensuring(Effect.sync(() => targets.delete(runId))));
10578
- const abandonOnError = (runId, target, operation) => operation.pipe(Effect.catchAll((error) => failTarget(runId, target).pipe(Effect.catchAll(() => Effect.void), Effect.flatMap(() => Effect.fail(error)))));
10579
+ const abandonOnError = (runId, target, operation) => operation.pipe(Effect.catch((error) => failTarget(runId, target).pipe(Effect.catch(() => Effect.void), Effect.flatMap(() => Effect.fail(error)))));
10579
10580
  const flushReasoning = (target, timestamp) => {
10580
10581
  if (target.pendingReasoning.length === 0) return [];
10581
10582
  const delta = readRedactedString(redact(target.pendingReasoning));
@@ -10987,7 +10988,7 @@ function createMessageGateway(options) {
10987
10988
  case "resolve_interaction": return yield* resolveInteraction(action);
10988
10989
  case "prompt":
10989
10990
  markSeen(key);
10990
- return yield* executePrompt(execution, observation, presentation, action).pipe(Effect.catchAll((error) => {
10991
+ return yield* executePrompt(execution, observation, presentation, action).pipe(Effect.catch((error) => {
10991
10992
  if (error instanceof AgentRunCancelled) return Effect.succeed({
10992
10993
  cancelled: true,
10993
10994
  messageId: action.messageId,
@@ -10997,7 +10998,7 @@ function createMessageGateway(options) {
10997
10998
  return Effect.fail(error);
10998
10999
  }));
10999
11000
  }
11000
- }).pipe(Effect.catchAll((error) => {
11001
+ }).pipe(Effect.catch((error) => {
11001
11002
  if (error instanceof AgentRunCancelled && (direct || action.type === "prompt")) return Effect.succeed({
11002
11003
  cancelled: true,
11003
11004
  messageId: action.messageId,
@@ -11450,7 +11451,7 @@ function parseCancelRunCommand(messageId, text) {
11450
11451
  //#region src/adapters/feishu/conversation-session/feishu-session-store.ts
11451
11452
  function createFeishuSessionStore(options = {}) {
11452
11453
  const records = recoverFeishuSessionRecords(options.initial ?? []);
11453
- const serial = Effect.unsafeMakeSemaphore(1);
11454
+ const serial = Semaphore.makeUnsafe(1);
11454
11455
  return {
11455
11456
  current: (baseSessionKey) => {
11456
11457
  validateFeishuBaseSessionKey(baseSessionKey);
@@ -11666,25 +11667,25 @@ function createFeishuAgentDaemon(options) {
11666
11667
  replyReset: (messageId, hadActiveRun) => sideEffectsDisabled || !options.reply ? Effect.void : options.reply(messageId, hadActiveRun ? "已停止当前任务并开启新会话。请发送下一条指令。" : "已开启新会话。请发送下一条指令。"),
11667
11668
  replySteered: (messageId) => {
11668
11669
  if (sideEffectsDisabled) return Effect.void;
11669
- if (options.acknowledgeSteering) return options.acknowledgeSteering(messageId).pipe(Effect.catchAll(() => options.reply ? options.reply(messageId, STEERING_ACK_TEXT) : Effect.void));
11670
+ if (options.acknowledgeSteering) return options.acknowledgeSteering(messageId).pipe(Effect.catch(() => options.reply ? options.reply(messageId, STEERING_ACK_TEXT) : Effect.void));
11670
11671
  return options.reply ? options.reply(messageId, STEERING_ACK_TEXT) : Effect.void;
11671
11672
  }
11672
11673
  }
11673
11674
  };
11674
11675
  };
11675
11676
  const trySteerMessage = (payload) => Effect.gen(function* () {
11676
- const resetDirective = yield* readFeishuSessionResetDirective(payload, messageIntakeOptions).pipe(Effect.either);
11677
- if (Either.isRight(resetDirective) && resetDirective.right?.prompt) {
11678
- cancelAttachmentPreparation(resetDirective.right.previousSessionKey);
11677
+ const resetDirective = yield* readFeishuSessionResetDirective(payload, messageIntakeOptions).pipe(Effect.result);
11678
+ if (Result.isSuccess(resetDirective) && resetDirective.success?.prompt) {
11679
+ cancelAttachmentPreparation(resetDirective.success.previousSessionKey);
11679
11680
  yield* execution.cancelActiveRun({
11680
11681
  reason: "Feishu new session command",
11681
- sessionKey: resetDirective.right.previousSessionKey
11682
+ sessionKey: resetDirective.success.previousSessionKey
11682
11683
  });
11683
11684
  return;
11684
11685
  }
11685
- const parsed = yield* createAgentCommandFromFeishuMessage(payload, messageIntakeOptions).pipe(Effect.either);
11686
- if (Either.isLeft(parsed)) return;
11687
- const inbound = parsed.right;
11686
+ const parsed = yield* createAgentCommandFromFeishuMessage(payload, messageIntakeOptions).pipe(Effect.result);
11687
+ if (Result.isFailure(parsed)) return;
11688
+ const inbound = parsed.success;
11688
11689
  const { observation, presentation, reply } = buildPorts(false);
11689
11690
  if (inbound.type === "cancel_run") {
11690
11691
  cancelPreparationForCommand(inbound);
@@ -11712,27 +11713,27 @@ function createFeishuAgentDaemon(options) {
11712
11713
  return steered ? fromGatewayResult(steered) : void 0;
11713
11714
  });
11714
11715
  const prepareControlMessage = (payload, phase = "admission") => Effect.gen(function* () {
11715
- const resetDirective = yield* readFeishuSessionResetDirective(payload, messageIntakeOptions).pipe(Effect.either);
11716
+ const resetDirective = yield* readFeishuSessionResetDirective(payload, messageIntakeOptions).pipe(Effect.result);
11716
11717
  const prepared = () => ({ execute: () => trySteerMessage(payload) });
11717
- if (Either.isLeft(resetDirective)) yield* failCausedIntake(resetDirective.left);
11718
- if (Either.isRight(resetDirective) && resetDirective.right) {
11719
- if (resetDirective.right.prompt.length > 0) {
11718
+ if (Result.isFailure(resetDirective)) yield* failCausedIntake(resetDirective.failure);
11719
+ if (Result.isSuccess(resetDirective) && resetDirective.success) {
11720
+ if (resetDirective.success.prompt.length > 0) {
11720
11721
  if (phase === "processing") return;
11721
- const activeRunId = execution.getActiveRun ? yield* execution.getActiveRun({ sessionKey: resetDirective.right.previousSessionKey }) : void 0;
11722
- const preparing = options.attachmentPreparation?.hasActive?.(resetDirective.right.previousSessionKey) ?? false;
11722
+ const activeRunId = execution.getActiveRun ? yield* execution.getActiveRun({ sessionKey: resetDirective.success.previousSessionKey }) : void 0;
11723
+ const preparing = options.attachmentPreparation?.hasActive?.(resetDirective.success.previousSessionKey) ?? false;
11723
11724
  return activeRunId || preparing ? prepared() : void 0;
11724
11725
  }
11725
11726
  return prepared();
11726
11727
  }
11727
11728
  if (feishuMessageRequiresWorkerPreparation(payload)) return;
11728
- const intake = yield* describeFeishuMessageIntake(payload, messageIntakeOptions).pipe(Effect.either);
11729
- if (Either.isLeft(intake)) {
11730
- yield* failCausedIntake(intake.left);
11729
+ const intake = yield* describeFeishuMessageIntake(payload, messageIntakeOptions).pipe(Effect.result);
11730
+ if (Result.isFailure(intake)) {
11731
+ yield* failCausedIntake(intake.failure);
11731
11732
  return;
11732
11733
  }
11733
- if (intake.right.commandType !== "prompt") return prepared();
11734
+ if (intake.success.commandType !== "prompt") return prepared();
11734
11735
  if (!execution.getActiveRun) return;
11735
- return (yield* execution.getActiveRun({ sessionKey: intake.right.sessionKey })) ? prepared() : void 0;
11736
+ return (yield* execution.getActiveRun({ sessionKey: intake.success.sessionKey })) ? prepared() : void 0;
11736
11737
  });
11737
11738
  return {
11738
11739
  describeMessageIntake: (payload) => describeFeishuMessageIntake(payload, {
@@ -11815,7 +11816,7 @@ function createFeishuAgentDaemon(options) {
11815
11816
  });
11816
11817
  if (!result) return yield* Effect.fail(/* @__PURE__ */ new Error("gateway returned no result for prompt"));
11817
11818
  return fromGatewayResult(result);
11818
- }).pipe(Effect.catchAll((error) => {
11819
+ }).pipe(Effect.catch((error) => {
11819
11820
  if (error instanceof AgentRunCancelled) return Effect.succeed({
11820
11821
  cancelled: true,
11821
11822
  messageId: payload.event.message.message_id,
@@ -11975,7 +11976,7 @@ function createDurableQueue(options) {
11975
11976
  const maxAttempts = positiveInteger(options.maxAttempts ?? 5, "maximum attempts");
11976
11977
  const maxPending = positiveInteger(options.maxPending ?? 100, "capacity");
11977
11978
  const retryDelayMs = nonNegativeInteger(options.retryDelayMs ?? 0, "retry delay");
11978
- const executePreparedAction = (prepared, deliveryId, leaseId) => prepared.execute().pipe(Effect.catchAll((error) => options.clock.now().pipe(Effect.flatMap((failedAt) => options.store.fail({
11979
+ const executePreparedAction = (prepared, deliveryId, leaseId) => prepared.execute().pipe(Effect.catch((error) => options.clock.now().pipe(Effect.flatMap((failedAt) => options.store.fail({
11979
11980
  failedAt,
11980
11981
  id: deliveryId,
11981
11982
  leaseId,
@@ -12068,7 +12069,7 @@ function createDurableQueue(options) {
12068
12069
  const actionResult = yield* executePreparedAction(preparedAction, delivery.id, claimedLeaseId);
12069
12070
  if (actionResult !== void 0) return yield* completeHandled(actionResult);
12070
12071
  }
12071
- const result = yield* options.handleDelivery(delivery.payload, delivery.metadata).pipe(Effect.catchAll((error) => options.clock.now().pipe(Effect.flatMap((failedAt) => options.store.fail({
12072
+ const result = yield* options.handleDelivery(delivery.payload, delivery.metadata).pipe(Effect.catch((error) => options.clock.now().pipe(Effect.flatMap((failedAt) => options.store.fail({
12072
12073
  availableAt: new Date(Date.parse(failedAt) + retryDelayMs).toISOString(),
12073
12074
  failedAt,
12074
12075
  id: delivery.id,
@@ -12171,10 +12172,10 @@ function createDurableQueueWorker(options) {
12171
12172
  const results = [];
12172
12173
  while (options.queue.pending() > 0) {
12173
12174
  const batchSize = Math.min(concurrency, options.queue.pending());
12174
- const outcomes = yield* Effect.all(Array.from({ length: batchSize }, () => options.queue.drainOne().pipe(Effect.either)), { concurrency: "unbounded" });
12175
- const failures = outcomes.filter(Either.isLeft).map((outcome) => outcome.left);
12175
+ const outcomes = yield* Effect.all(Array.from({ length: batchSize }, () => options.queue.drainOne().pipe(Effect.result)), { concurrency: "unbounded" });
12176
+ const failures = outcomes.filter(Result.isFailure).map((outcome) => outcome.failure);
12176
12177
  if (failures.length > 0) return yield* Effect.fail(failures.length === 1 ? failures[0] : new AggregateError(failures, "Inbound Delivery batch failed"));
12177
- const batch = outcomes.filter(Either.isRight).map((outcome) => outcome.right);
12178
+ const batch = outcomes.filter(Result.isSuccess).map((outcome) => outcome.success);
12178
12179
  if (batch.every((result) => !result.handled)) break;
12179
12180
  for (const result of batch) if (result.handled) results.push(result);
12180
12181
  }
@@ -12187,7 +12188,7 @@ function createDurableQueueWorker(options) {
12187
12188
  function createDurableQueueWorkerLoop(options) {
12188
12189
  if (!Number.isSafeInteger(options.intervalMs) || options.intervalMs < 0) throw new Error("Inbound Delivery worker interval must be a non-negative integer");
12189
12190
  const report = (error) => options.onError?.(error) ?? Effect.void;
12190
- const continueAfterReporting = (effect) => effect.pipe(Effect.asVoid, Effect.catchAll(report));
12191
+ const continueAfterReporting = (effect) => effect.pipe(Effect.asVoid, Effect.catch(report));
12191
12192
  return Effect.forever(Effect.suspend(() => continueAfterReporting(options.drainAvailable()).pipe(Effect.flatMap(() => continueAfterReporting(options.sleep(options.intervalMs))))));
12192
12193
  }
12193
12194
  //#endregion
@@ -12321,7 +12322,7 @@ function createInMemoryDurableQueueStore(options = {}) {
12321
12322
  if (deliveries.has(restored.id)) throw new Error(`duplicate Inbound Delivery snapshot: ${restored.id}`);
12322
12323
  deliveries.set(restored.id, restored);
12323
12324
  }
12324
- const serial = Effect.unsafeMakeSemaphore(1);
12325
+ const serial = Semaphore.makeUnsafe(1);
12325
12326
  let lastLaneKey;
12326
12327
  const save = (current, delivery) => {
12327
12328
  const snapshot = readInboundDeliverySnapshot(current === void 0 ? delivery : resolveUpdate(current, delivery));
@@ -12473,7 +12474,7 @@ function createFeishuEventHandlers(options) {
12473
12474
  } };
12474
12475
  if (options.cardActions) return {
12475
12476
  ...handlers,
12476
- "card.action.trigger": (payload) => Effect.runPromise(options.cardActions.handleCardAction(payload).pipe(Effect.map(createFeishuCardActionCallbackResponse), Effect.catchAll(() => Effect.succeed(createFeishuCardActionErrorResponse()))))
12477
+ "card.action.trigger": (payload) => Effect.runPromise(options.cardActions.handleCardAction(payload).pipe(Effect.map(createFeishuCardActionCallbackResponse), Effect.catch(() => Effect.succeed(createFeishuCardActionErrorResponse()))))
12477
12478
  };
12478
12479
  return handlers;
12479
12480
  }
@@ -12530,7 +12531,7 @@ function toStandardRunResult(result) {
12530
12531
  function createFeishuAgentExecutionBridge(options) {
12531
12532
  const activeNamespacedSessions = /* @__PURE__ */ new Set();
12532
12533
  const cancellationRequests = /* @__PURE__ */ new Map();
12533
- const cancellationSerial = Effect.unsafeMakeSemaphore(1);
12534
+ const cancellationSerial = Semaphore.makeUnsafe(1);
12534
12535
  const activeHandles = /* @__PURE__ */ new Set();
12535
12536
  const activeRuns = /* @__PURE__ */ new Map();
12536
12537
  let stopping = false;
@@ -12543,7 +12544,7 @@ function createFeishuAgentExecutionBridge(options) {
12543
12544
  if (current) return current;
12544
12545
  const completion = yield* Deferred.make();
12545
12546
  cancellationRequests.set(runId, completion);
12546
- yield* Effect.forkDaemon(Effect.suspend(() => options.cancel({
12547
+ yield* Effect.forkDetach(Effect.suspend(() => options.cancel({
12547
12548
  reason: "endpoint_stopping",
12548
12549
  runId,
12549
12550
  sessionKey
@@ -12637,17 +12638,17 @@ function createFeishuAgentExecutionBridge(options) {
12637
12638
  if (activeRunId) activeRuns.delete(activeRunId);
12638
12639
  activeHandles.delete(completion);
12639
12640
  activeNamespacedSessions.delete(sessionKey);
12640
- }).pipe(Effect.zipRight(Deferred.succeed(completion, void 0)))));
12641
- yield* Effect.forkDaemon(restore(run).pipe(Effect.exit, Effect.flatMap((exit) => Deferred.done(outcome, exit))));
12641
+ }).pipe(Effect.andThen(Deferred.succeed(completion, void 0)))));
12642
+ yield* Effect.forkDetach(restore(run).pipe(Effect.exit, Effect.flatMap((exit) => Deferred.done(outcome, exit))));
12642
12643
  return yield* restore(Deferred.await(outcome));
12643
12644
  }))
12644
12645
  }
12645
12646
  };
12646
12647
  }
12647
12648
  function waitUntilDeadline(effect, deadline, message) {
12648
- return effect.pipe(Effect.interruptible, Effect.timeoutFail({
12649
+ return effect.pipe(Effect.interruptible, Effect.timeoutOrElse({
12649
12650
  duration: Math.max(0, deadline - Date.now()),
12650
- onTimeout: () => new Error(message)
12651
+ orElse: () => Effect.fail(new Error(message))
12651
12652
  }));
12652
12653
  }
12653
12654
  function namespaceSessionKey(namespace, sessionKey) {
@@ -12793,7 +12794,7 @@ function createFeishuDeploymentEndpoint(options) {
12793
12794
  let stopping = false;
12794
12795
  const activeDrains = /* @__PURE__ */ new Set();
12795
12796
  const awaitDrain = (drain) => Fiber.join(drain).pipe(Effect.onExit((exit) => Effect.sync(() => {
12796
- if (exit._tag === "Success" || !Cause.isInterruptedOnly(exit.cause)) activeDrains.delete(drain);
12797
+ if (exit._tag === "Success" || !Cause.hasInterruptsOnly(exit.cause)) activeDrains.delete(drain);
12797
12798
  })));
12798
12799
  const stopCardRollover = options.cardRollover ? Effect.tryPromise({
12799
12800
  try: () => Promise.resolve(options.cardRollover.stop()),
@@ -12811,7 +12812,7 @@ function createFeishuDeploymentEndpoint(options) {
12811
12812
  });
12812
12813
  const workerLoop = createEffectLoopDriver(createDurableQueueWorkerLoop({
12813
12814
  drainAvailable: () => Effect.uninterruptibleMask((restore) => Effect.gen(function* () {
12814
- const drain = yield* Effect.forkDaemon(worker.drainAvailable().pipe(Effect.interruptible));
12815
+ const drain = yield* Effect.forkDetach(worker.drainAvailable().pipe(Effect.interruptible));
12815
12816
  activeDrains.add(drain);
12816
12817
  return yield* restore(awaitDrain(drain));
12817
12818
  })),
@@ -12825,7 +12826,7 @@ function createFeishuDeploymentEndpoint(options) {
12825
12826
  handlers: createFeishuEventHandlers({
12826
12827
  cardActions: daemon,
12827
12828
  queue: {
12828
- accept: (payload) => shouldAcceptFeishuEndpointMessage(payload, options.groupPolicy, options.botOpenId) ? inbound.accept(payload).pipe(Effect.tap((result) => result.accepted || result.reason !== "capacity" || !options.onCapacityExceeded ? Effect.void : options.onCapacityExceeded(payload).pipe(Effect.catchAllCause((cause) => Cause.isInterruptedOnly(cause) ? Effect.interrupt : reportWorkerError(Cause.squash(cause)))))) : Effect.succeed({
12829
+ accept: (payload) => shouldAcceptFeishuEndpointMessage(payload, options.groupPolicy, options.botOpenId) ? inbound.accept(payload).pipe(Effect.tap((result) => result.accepted || result.reason !== "capacity" || !options.onCapacityExceeded ? Effect.void : options.onCapacityExceeded(payload).pipe(Effect.catchCause((cause) => Cause.hasInterruptsOnly(cause) ? Effect.interrupt : reportWorkerError(Cause.squash(cause)))))) : Effect.succeed({
12829
12830
  accepted: false,
12830
12831
  messageId: payload.event.message.message_id,
12831
12832
  pending: queue.pending(),
@@ -12853,7 +12854,7 @@ function createFeishuDeploymentEndpoint(options) {
12853
12854
  catch: (error) => error
12854
12855
  });
12855
12856
  workerLoop.start();
12856
- }).pipe(Effect.catchAllCause((startCause) => Effect.uninterruptible(Effect.gen(function* () {
12857
+ }).pipe(Effect.catchCause((startCause) => Effect.uninterruptible(Effect.gen(function* () {
12857
12858
  const cleanupErrors = yield* settleCleanup([
12858
12859
  Effect.tryPromise({
12859
12860
  try: () => websocket.stop(),
@@ -12873,11 +12874,11 @@ function createFeishuDeploymentEndpoint(options) {
12873
12874
  const timeoutMs = options.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS;
12874
12875
  const deadline = Date.now() + timeoutMs;
12875
12876
  let executionStopped = false;
12876
- const websocketStop = yield* Effect.fork(Effect.tryPromise({
12877
+ const websocketStop = yield* Effect.forkChild(Effect.tryPromise({
12877
12878
  try: () => websocket.stop(),
12878
12879
  catch: (error) => error
12879
12880
  }));
12880
- const workerStop = yield* Effect.fork(Effect.tryPromise({
12881
+ const workerStop = yield* Effect.forkChild(Effect.tryPromise({
12881
12882
  try: () => workerLoop.stop(),
12882
12883
  catch: (error) => error
12883
12884
  }));
@@ -12887,9 +12888,9 @@ function createFeishuDeploymentEndpoint(options) {
12887
12888
  executionStopped = true;
12888
12889
  }))),
12889
12890
  Fiber.join(workerStop),
12890
- Effect.suspend(() => executionStopped ? Effect.all([...activeDrains].map(awaitDrain), { concurrency: "unbounded" }).pipe(Effect.interruptible, Effect.timeoutFail({
12891
+ Effect.suspend(() => executionStopped ? Effect.all([...activeDrains].map(awaitDrain), { concurrency: "unbounded" }).pipe(Effect.interruptible, Effect.timeoutOrElse({
12891
12892
  duration: Math.max(0, deadline - Date.now()),
12892
- onTimeout: () => /* @__PURE__ */ new Error(`Feishu Inbox drain did not stop within ${timeoutMs}ms`)
12893
+ orElse: () => Effect.fail(/* @__PURE__ */ new Error(`Feishu Inbox drain did not stop within ${timeoutMs}ms`))
12893
12894
  })) : Effect.void),
12894
12895
  stopCardRollover
12895
12896
  ]);
@@ -12909,13 +12910,989 @@ function settleCleanup(steps) {
12909
12910
  });
12910
12911
  }
12911
12912
  //#endregion
12913
+ //#region src/adapters/feishu/inbound-delivery/jsonl-inbound-delivery-store.ts
12914
+ function openJsonlInboundDeliveryStore(options) {
12915
+ const codecOptions = {
12916
+ ...options.acceptLegacySameAttemptClaims === void 0 ? {} : { acceptLegacySameAttemptClaims: options.acceptLegacySameAttemptClaims },
12917
+ label: options.label ?? "Inbound Delivery",
12918
+ payload: options.payload
12919
+ };
12920
+ return Effect.gen(function* () {
12921
+ const raw = yield* Effect.tryPromise({
12922
+ try: () => readPersistenceFile(options.filePath),
12923
+ catch: (cause) => cause
12924
+ });
12925
+ return createInMemoryDurableQueueStore({
12926
+ initial: yield* Effect.try({
12927
+ try: () => decodeInboundDeliverySnapshots(raw, codecOptions),
12928
+ catch: (cause) => cause
12929
+ }),
12930
+ persist: (delivery) => appendInboundDeliverySnapshot(options.filePath, delivery)
12931
+ });
12932
+ });
12933
+ }
12934
+ function decodeInboundDeliverySnapshots(raw, options) {
12935
+ if (raw === void 0) return [];
12936
+ const latest = /* @__PURE__ */ new Map();
12937
+ for (const [index, line] of raw.split("\n").entries()) {
12938
+ if (line.trim().length === 0) continue;
12939
+ let parsed;
12940
+ try {
12941
+ parsed = JSON.parse(line);
12942
+ } catch {
12943
+ throw new Error(`invalid ${options.label} snapshot at line ${index + 1}`);
12944
+ }
12945
+ if (!isEnvelope(parsed)) throw new Error(`invalid ${options.label} snapshot at line ${index + 1}`);
12946
+ let delivery;
12947
+ try {
12948
+ delivery = decodeDelivery(parsed.delivery, options.payload);
12949
+ } catch {
12950
+ throw new Error(`invalid ${options.label} snapshot at line ${index + 1}`);
12951
+ }
12952
+ const previous = latest.get(delivery.id);
12953
+ if (delivery.revision !== (previous?.revision ?? 0) + 1) throw new Error(`invalid ${options.label} revision at line ${index + 1}`);
12954
+ try {
12955
+ if (previous) {
12956
+ if (!isValidPersistedTransition(previous, delivery, options.acceptLegacySameAttemptClaims ?? false)) throw new Error("transition");
12957
+ } else assertNewInboundDelivery(delivery);
12958
+ } catch {
12959
+ throw new Error(`invalid ${options.label} transition at line ${index + 1}`);
12960
+ }
12961
+ latest.set(delivery.id, delivery);
12962
+ }
12963
+ return [...latest.values()];
12964
+ }
12965
+ function encodeInboundDeliverySnapshot(delivery) {
12966
+ const snapshot = readInboundDeliverySnapshot(delivery);
12967
+ const persisted = {
12968
+ acceptedAt: snapshot.acceptedAt,
12969
+ attempts: snapshot.attempts,
12970
+ id: snapshot.id,
12971
+ laneKey: snapshot.laneKey,
12972
+ ...snapshot.metadata === void 0 ? {} : { options: snapshot.metadata },
12973
+ payload: snapshot.payload,
12974
+ ...snapshot.recovery === void 0 ? {} : { recovery: snapshot.recovery },
12975
+ revision: snapshot.revision,
12976
+ state: snapshot.state
12977
+ };
12978
+ return JSON.stringify({
12979
+ delivery: persisted,
12980
+ version: 1
12981
+ });
12982
+ }
12983
+ function decodeDelivery(value, payloadCodec) {
12984
+ if (!isRecord(value) || typeof value.acceptedAt !== "string" || typeof value.attempts !== "number" || typeof value.id !== "string" || typeof value.laneKey !== "string" || typeof value.revision !== "number") throw new Error("invalid Inbound Delivery snapshot");
12985
+ const metadata = payloadCodec.decodeMetadata(value.options);
12986
+ return readInboundDeliverySnapshot({
12987
+ acceptedAt: value.acceptedAt,
12988
+ attempts: value.attempts,
12989
+ id: value.id,
12990
+ laneKey: value.laneKey,
12991
+ ...metadata === void 0 ? {} : { metadata },
12992
+ payload: payloadCodec.decodePayload(value.payload),
12993
+ ...value.recovery === void 0 ? {} : { recovery: value.recovery },
12994
+ revision: value.revision,
12995
+ state: value.state
12996
+ });
12997
+ }
12998
+ function isEnvelope(value) {
12999
+ return isRecord(value) && value.version === 1 && "delivery" in value;
13000
+ }
13001
+ function isValidPersistedTransition(previous, next, acceptLegacySameAttemptClaims) {
13002
+ if (isValidInboundDeliveryTransition(previous, next)) return true;
13003
+ if (!acceptLegacySameAttemptClaims || previous.state.status !== "pending" || next.state.status !== "leased" || next.attempts !== previous.attempts) return false;
13004
+ return isValidInboundDeliveryTransition(previous, {
13005
+ ...next,
13006
+ attempts: next.attempts + 1
13007
+ });
13008
+ }
13009
+ function appendInboundDeliverySnapshot(filePath, delivery) {
13010
+ return Effect.tryPromise({
13011
+ try: async () => {
13012
+ await mkdir(dirname(filePath), { recursive: true });
13013
+ await appendFile(filePath, `${encodeInboundDeliverySnapshot(delivery)}\n`, "utf8");
13014
+ },
13015
+ catch: (cause) => cause
13016
+ });
13017
+ }
13018
+ //#endregion
13019
+ //#region src/adapters/feishu/routing/feishu-conversation-router.ts
13020
+ const CAPACITY_REFUSAL = "队列已满,请用新消息重发;本条未提交。";
13021
+ const UNSUPPORTED_TYPE_REFUSAL = "会话模式暂不支持该消息类型。";
13022
+ /**
13023
+ * Single-entry Feishu conversation router: every message and callback is fenced
13024
+ * by the durable owner ledger before any destination runs, and the mode capture
13025
+ * plus owner admission share one bridge admission turn.
13026
+ */
13027
+ function createFeishuConversationRouter(options) {
13028
+ const mode = options.bridge.conversation?.mode;
13029
+ const allowed = (identity) => options.actors.includes(identity.actorId) && options.chats.includes(identity.chatId);
13030
+ const freeze = (reason, messageId) => Effect.sync(() => {
13031
+ mode?.freeze();
13032
+ options.onObservation?.({
13033
+ ...messageId === void 0 ? {} : { messageId },
13034
+ type: "route_frozen"
13035
+ });
13036
+ console.error(`Herdr route frozen: ${reason}`);
13037
+ });
13038
+ const freezeVerdict = (reason, messageId) => Effect.gen(function* () {
13039
+ yield* freeze(reason, messageId);
13040
+ return { kind: "frozen" };
13041
+ });
13042
+ /**
13043
+ * Refusals for owner uncertainty use a stable internal claim key: claiming the
13044
+ * original id would fabricate a Herdr fence that conflicts with a durable Pi
13045
+ * owner after restart, so the original owner identity stays untouched.
13046
+ */
13047
+ const refusalClaimId = (identity) => `route-refusal:${JSON.stringify([options.appId, identity.messageId])}`;
13048
+ const refusalMessage = (identity) => ({
13049
+ id: refusalClaimId(identity),
13050
+ actorId: identity.actorId,
13051
+ chatId: identity.chatId,
13052
+ sentAt: identity.sentAt,
13053
+ text: ""
13054
+ });
13055
+ /** One admission turn: legacy fences → owner lookup → capture → durable owner → admit captured. */
13056
+ const admitSequence = (input) => Effect.gen(function* () {
13057
+ const { identity } = input;
13058
+ const legacy = yield* options.legacyOwnerOf(identity.messageId);
13059
+ if (legacy === "conflict") return yield* freezeVerdict("legacy Pi inbox and Herdr claim conflict", identity.messageId);
13060
+ const lookup = yield* Effect.result(options.ledger.lookup(identity.messageId));
13061
+ if (Result.isFailure(lookup)) return yield* freezeVerdict("owner ledger lookup failed", identity.messageId);
13062
+ if (lookup.success.kind === "opaque-fence") return yield* freezeVerdict("owner record is an opaque fence", identity.messageId);
13063
+ if (lookup.success.kind === "record") {
13064
+ const record = lookup.success.record;
13065
+ if (record.actorId !== identity.actorId || record.chatId !== identity.chatId) return yield* freezeVerdict("owner record does not match the incoming message source", identity.messageId);
13066
+ if (legacy === "pi" && record.decision === "herdr" || legacy === "herdr" && record.decision === "pi") return yield* freezeVerdict("historical fence and owner record disagree", identity.messageId);
13067
+ options.onObservation?.({
13068
+ messageId: identity.messageId,
13069
+ type: "route_skipped"
13070
+ });
13071
+ return record.decision === "pi" ? { kind: "pi" } : { kind: "skip" };
13072
+ }
13073
+ if (legacy === "herdr") {
13074
+ options.onObservation?.({
13075
+ messageId: identity.messageId,
13076
+ type: "route_skipped"
13077
+ });
13078
+ return { kind: "skip" };
13079
+ }
13080
+ let captured;
13081
+ let unsupportedType = false;
13082
+ if (input.parsed) captured = options.bridge.captureInAdmission(input.parsed);
13083
+ else if (legacy === "none" && mode?.isFrozen() && allowed(identity)) return { kind: "frozen" };
13084
+ else if (legacy === "none" && mode?.stateOf(identity.actorId, identity.chatId)) unsupportedType = true;
13085
+ const decision = legacy === "pi" || !unsupportedType && (captured === void 0 || captured.route.kind === "pass-through") ? "pi" : "herdr";
13086
+ const record = {
13087
+ appId: options.appId,
13088
+ messageId: identity.messageId,
13089
+ decision,
13090
+ actorId: identity.actorId,
13091
+ chatId: identity.chatId,
13092
+ at: Date.now(),
13093
+ ...decision === "herdr" && captured?.route.kind === "locked-send" ? { target: captured.route.agent } : {}
13094
+ };
13095
+ const admitted = yield* Effect.result(options.ledger.admit(record));
13096
+ if (Result.isFailure(admitted)) return yield* freezeVerdict("owner admission failed", identity.messageId);
13097
+ if (admitted.success.kind === "existing-opaque") return yield* freezeVerdict("owner admission found an opaque fence", identity.messageId);
13098
+ if (admitted.success.kind === "existing") {
13099
+ const existing = admitted.success.record;
13100
+ if (legacy === "pi" && existing.decision === "herdr") return yield* freezeVerdict("historical fence and owner record disagree", identity.messageId);
13101
+ options.onObservation?.({
13102
+ messageId: identity.messageId,
13103
+ type: "route_skipped"
13104
+ });
13105
+ return existing.decision === "pi" ? { kind: "pi" } : { kind: "skip" };
13106
+ }
13107
+ if (decision === "pi") return { kind: "pi" };
13108
+ if (unsupportedType || captured === void 0) return {
13109
+ kind: "unsupported-type",
13110
+ chatId: identity.chatId
13111
+ };
13112
+ const outcome = yield* Effect.result(options.bridge.admitCapturedInAdmission(captured));
13113
+ if (Result.isFailure(outcome)) return yield* freezeVerdict("herdr event admission failed", identity.messageId);
13114
+ return {
13115
+ kind: "herdr",
13116
+ entry: {
13117
+ message: captured.message,
13118
+ outcome: outcome.success
13119
+ }
13120
+ };
13121
+ });
13122
+ /**
13123
+ * Runs the sequence inside one admission turn and publishes the shared freeze
13124
+ * for every full cause BEFORE the permit is released, so already queued work
13125
+ * and later captures observe the exact flag.
13126
+ */
13127
+ const admitInAdmission = (input) => options.bridge.withAdmission(Effect.gen(function* () {
13128
+ const exit = yield* Effect.exit(admitSequence(input));
13129
+ if (Exit.isFailure(exit)) return yield* freezeVerdict("admission sequence failed", input.identity.messageId);
13130
+ return exit.value;
13131
+ }));
13132
+ /**
13133
+ * Owner uncertainty freezes; the visible refusal is admitted in a separate,
13134
+ * non-nested turn via the bridge's own frozen-refusal capture, so freshness,
13135
+ * authorization and the event claim stay with existing semantics. Delivery
13136
+ * happens only outside admission through the tracked reply lane. A failing
13137
+ * refusal claim keeps the freeze and reports a diagnostic: there is no
13138
+ * direct undeduped chat fallback and no destination retry.
13139
+ */
13140
+ const deliverOwnerUncertainRefusal = async (identity) => {
13141
+ let outcome;
13142
+ try {
13143
+ outcome = await Effect.runPromise(options.bridge.withAdmission(Effect.gen(function* () {
13144
+ const captured = options.bridge.captureInAdmission(refusalMessage(identity));
13145
+ return yield* options.bridge.admitCapturedInAdmission(captured);
13146
+ })));
13147
+ } catch {
13148
+ outcome = void 0;
13149
+ }
13150
+ if (outcome !== void 0 && outcome.kind === "done" && outcome.result.kind === "reply") {
13151
+ await options.target.reply(identity.chatId, outcome.result.reply.text);
13152
+ return true;
13153
+ }
13154
+ options.onObservation?.({
13155
+ messageId: identity.messageId,
13156
+ type: "refusal_undelivered"
13157
+ });
13158
+ console.error(`Herdr owner-uncertain refusal not delivered for ${identity.messageId}`);
13159
+ return false;
13160
+ };
13161
+ const route = async (identity, parsed) => {
13162
+ let verdict;
13163
+ try {
13164
+ verdict = await Effect.runPromise(admitInAdmission({
13165
+ identity,
13166
+ parsed
13167
+ }));
13168
+ } catch {
13169
+ verdict = { kind: "frozen" };
13170
+ }
13171
+ switch (verdict.kind) {
13172
+ case "pi": return { kind: "pi" };
13173
+ case "skip": return { kind: "skip" };
13174
+ case "frozen":
13175
+ await deliverOwnerUncertainRefusal(identity);
13176
+ return { kind: "frozen" };
13177
+ case "unsupported-type":
13178
+ await options.target.reply(verdict.chatId, UNSUPPORTED_TYPE_REFUSAL);
13179
+ return { kind: "consumed" };
13180
+ case "herdr":
13181
+ if (!await options.target.execute(verdict.entry)) await options.target.reply(verdict.entry.message.chatId, CAPACITY_REFUSAL);
13182
+ return { kind: "consumed" };
13183
+ }
13184
+ };
13185
+ const routeAwaited = async (identity, parsed) => {
13186
+ if (options.target.runAdmission === void 0) return route(identity, parsed);
13187
+ const admission = await options.target.runAdmission(() => route(identity, parsed));
13188
+ if (admission.kind === "stopped") throw new Error("Herdr intake is stopped; event not admitted");
13189
+ return admission.value;
13190
+ };
13191
+ const acceptedResult = (messageId) => ({
13192
+ accepted: true,
13193
+ messageId,
13194
+ pending: 0
13195
+ });
13196
+ const skippedResult = (messageId) => ({
13197
+ accepted: false,
13198
+ messageId,
13199
+ pending: 0,
13200
+ reason: "duplicate"
13201
+ });
13202
+ const refusedResult = (messageId) => ({
13203
+ accepted: false,
13204
+ messageId,
13205
+ pending: 0,
13206
+ reason: "ignored"
13207
+ });
13208
+ const deadCardResponse = () => ({ toast: {
13209
+ content: "This card cannot be handled; run /herdr again for a fresh one.",
13210
+ i18n: {
13211
+ en_us: "This card cannot be handled; run /herdr again for a fresh one.",
13212
+ zh_cn: "此卡片已失效或无法识别;请重新执行 /herdr 获取新卡片。"
13213
+ },
13214
+ type: "error"
13215
+ } });
13216
+ const notReceivedCardResponse = () => ({ toast: {
13217
+ content: "The intake is full; this click was not received. Try again in a moment.",
13218
+ i18n: {
13219
+ en_us: "The intake is full; this click was not received. Try again in a moment.",
13220
+ zh_cn: "处理通道已满,本次点击未接收;请稍后重试。"
13221
+ },
13222
+ type: "error"
13223
+ } });
13224
+ const frozenCardResponse = () => ({ toast: {
13225
+ content: "Routing is paused; this click was not executed.",
13226
+ i18n: {
13227
+ en_us: "Routing is paused; this click was not executed.",
13228
+ zh_cn: "会话路由已暂停;本次点击未执行。"
13229
+ },
13230
+ type: "warning"
13231
+ } });
13232
+ const receivedCardResponse = () => ({ toast: {
13233
+ content: "Received; the result arrives as a chat reply.",
13234
+ i18n: {
13235
+ en_us: "Received; the result arrives as a chat reply.",
13236
+ zh_cn: "已接收,结果将以会话回复送达。"
13237
+ },
13238
+ type: "info"
13239
+ } });
13240
+ const duplicateCardResponse = () => ({ toast: {
13241
+ content: "Already handled",
13242
+ i18n: {
13243
+ en_us: "Already handled",
13244
+ zh_cn: "该操作已处理过"
13245
+ },
13246
+ type: "info"
13247
+ } });
13248
+ const piCardErrorResponse = () => ({ toast: {
13249
+ content: "Card action failed",
13250
+ i18n: {
13251
+ en_us: "Card action failed",
13252
+ zh_cn: "卡片操作失败"
13253
+ },
13254
+ type: "error"
13255
+ } });
13256
+ return {
13257
+ /**
13258
+ * Message entry for the merged dispatcher table. Decides ownership before
13259
+ * any destination; the original Pi handler runs only for Pi-owned events.
13260
+ */
13261
+ routeMessage: (piReceive) => async (payload) => {
13262
+ const normalized = normalizeReceiveMessagePayload(payload);
13263
+ if (!shouldAcceptFeishuEndpointMessage(normalized, options.groupPolicy, options.botOpenId)) return piReceive(payload);
13264
+ const identity = readMessageIdentity(normalized.event);
13265
+ if (!identity) return piReceive(payload);
13266
+ const parsed = parseFeishuTerminalMessage(normalized.event);
13267
+ switch ((await routeAwaited(identity, parsed)).kind) {
13268
+ case "pi": return piReceive(payload);
13269
+ case "skip": return skippedResult(identity.messageId);
13270
+ case "frozen": return refusedResult(identity.messageId);
13271
+ case "consumed": return acceptedResult(identity.messageId);
13272
+ }
13273
+ },
13274
+ /**
13275
+ * Card callback entry: ownership comes only from explicit markers. Pi cards
13276
+ * always reach the original handler, in every mode and while frozen.
13277
+ */
13278
+ routeCardAction: (piCardAction) => async (event) => {
13279
+ const ownership = classifyFeishuCardOwnership(event);
13280
+ if (ownership === "pi") {
13281
+ if (!piCardAction) return piCardErrorResponse();
13282
+ return piCardAction(event);
13283
+ }
13284
+ if (ownership !== "herdr") return deadCardResponse();
13285
+ const message = parseFeishuTerminalAction(event, {});
13286
+ if (!message) return deadCardResponse();
13287
+ const identity = {
13288
+ actorId: message.actorId,
13289
+ chatId: message.chatId,
13290
+ messageId: message.id,
13291
+ messageType: "text",
13292
+ sentAt: message.sentAt
13293
+ };
13294
+ if (options.target.scheduleAdmission !== void 0) {
13295
+ const scheduled = await options.target.scheduleAdmission(() => route(identity, message).then(() => void 0));
13296
+ if (scheduled === "accepted") return receivedCardResponse();
13297
+ if (scheduled === "full") return notReceivedCardResponse();
13298
+ return frozenCardResponse();
13299
+ }
13300
+ switch ((await route(identity, message)).kind) {
13301
+ case "pi": return piCardAction ? piCardAction(event) : piCardErrorResponse();
13302
+ case "skip": return duplicateCardResponse();
13303
+ case "frozen": return frozenCardResponse();
13304
+ case "consumed": return receivedCardResponse();
13305
+ }
13306
+ },
13307
+ /** Bot menu events are Herdr-owned by their configured event_key. */
13308
+ routeMenuEvent: async (event, menuChats) => {
13309
+ const message = parseFeishuTerminalAction(event, menuChats);
13310
+ if (!message) return;
13311
+ await routeAwaited({
13312
+ actorId: message.actorId,
13313
+ chatId: message.chatId,
13314
+ messageId: message.id,
13315
+ messageType: "text",
13316
+ sentAt: message.sentAt
13317
+ }, message);
13318
+ }
13319
+ };
13320
+ }
13321
+ /**
13322
+ * Wraps the endpoint's real event dispatcher: Pi handlers stay under their own
13323
+ * keys, the three routed keys are replaced once, every other key is preserved,
13324
+ * and the underlying `register` return value reaches the WebSocket start
13325
+ * unchanged.
13326
+ */
13327
+ function createRoutedFeishuEventDispatcher(inner, route, menuChats) {
13328
+ return { register: (handlers) => {
13329
+ const merged = {
13330
+ ...handlers,
13331
+ "im.message.receive_v1": (payload) => route.message(handlers["im.message.receive_v1"])(payload),
13332
+ "card.action.trigger": (payload) => route.card(handlers["card.action.trigger"])(payload),
13333
+ "application.bot.menu_v6": (payload) => route.menu(payload, menuChats)
13334
+ };
13335
+ return inner.register(merged);
13336
+ } };
13337
+ }
13338
+ /** Normalized event shape: {message, sender} at the top level. */
13339
+ const messageIdentitySchema = Schema.Struct({
13340
+ message: Schema.optional(Schema.Struct({
13341
+ chat_id: Schema.optional(Schema.String),
13342
+ message_id: Schema.optional(Schema.String),
13343
+ message_type: Schema.optional(Schema.String),
13344
+ create_time: Schema.optional(Schema.String)
13345
+ })),
13346
+ sender: Schema.optional(Schema.Struct({ sender_id: Schema.optional(Schema.Struct({ open_id: Schema.optional(Schema.String) })) }))
13347
+ });
13348
+ const decodeMessageIdentity = Schema.decodeUnknownSync(messageIdentitySchema);
13349
+ /** Builds the gateway owner record identity from a normalized event payload. */
13350
+ function readMessageIdentity(event) {
13351
+ let decoded;
13352
+ try {
13353
+ decoded = decodeMessageIdentity(event);
13354
+ } catch {
13355
+ return;
13356
+ }
13357
+ const message = decoded.message;
13358
+ const messageId = message?.message_id;
13359
+ const chatId = message?.chat_id;
13360
+ const actorId = decoded.sender?.sender_id?.open_id;
13361
+ if (!messageId || !chatId || !actorId) return;
13362
+ return {
13363
+ actorId,
13364
+ chatId,
13365
+ messageId,
13366
+ messageType: message?.message_type ?? "",
13367
+ sentAt: Number(message?.create_time)
13368
+ };
13369
+ }
13370
+ //#endregion
13371
+ //#region src/adapters/outbound/persistence/message-gateway/file-route-admission-ledger.ts
13372
+ const admissionRecordSchema = Schema.Struct({
13373
+ version: Schema.Literal(1),
13374
+ appId: Schema.String,
13375
+ messageId: Schema.String,
13376
+ decision: Schema.Literals(["pi", "herdr"]),
13377
+ actorId: Schema.String,
13378
+ chatId: Schema.String,
13379
+ at: Schema.Number,
13380
+ target: Schema.optional(terminalAgentSchema)
13381
+ });
13382
+ const decodeAdmissionRecord = Schema.decodeUnknownSync(admissionRecordSchema);
13383
+ const isErrnoCode$1 = Schema.Struct({ code: Schema.String });
13384
+ /** Admissions and state directories reject any group/other permission or symlink. */
13385
+ function assertPrivateDirectory$1(info, label) {
13386
+ if (info.isSymbolicLink() || !info.isDirectory()) throw new Error(`${label} is not a regular directory`);
13387
+ if (info.uid !== process.getuid?.() || (info.mode & 63) !== 0) throw new Error(`Unsafe ${label} permissions or owner`);
13388
+ }
13389
+ function assertPrivateOwnerFile(info) {
13390
+ if (info.isSymbolicLink() || !info.isFile()) throw new Error("Route admission owner is not a regular file");
13391
+ if (info.uid !== process.getuid?.() || (info.mode & 127) !== 0) throw new Error("Unsafe route admission owner permissions or owner");
13392
+ }
13393
+ /**
13394
+ * Create the final-level admissions child inside an already-provisioned state
13395
+ * root (the shared claims opener owns the root) and sync the root entry durably.
13396
+ * A missing root is a startup failure with guidance — never an ancestor walk.
13397
+ */
13398
+ async function createPrivateChildDirectory(path, mode, label, stateRoot) {
13399
+ try {
13400
+ await mkdir(path, {
13401
+ recursive: false,
13402
+ mode
13403
+ });
13404
+ } catch (error) {
13405
+ if (Schema.is(isErrnoCode$1)(error) && error.code === "ENOENT") throw new Error(`Cannot create ${label}: the state directory ${stateRoot} does not exist; open the shared claims store first`);
13406
+ if (!(Schema.is(isErrnoCode$1)(error) && error.code === "EEXIST")) throw error;
13407
+ }
13408
+ assertPrivateDirectory$1(await lstat(path), label);
13409
+ }
13410
+ async function syncDirectoryEntry(path) {
13411
+ const handle = await open(path, "r");
13412
+ try {
13413
+ await handle.sync();
13414
+ } finally {
13415
+ await handle.close();
13416
+ }
13417
+ }
13418
+ async function syncStateDirectory(path, label) {
13419
+ try {
13420
+ await syncDirectoryEntry(path);
13421
+ } catch (error) {
13422
+ throw new Error(`Cannot sync ${label}`, { cause: error });
13423
+ }
13424
+ }
13425
+ /**
13426
+ * File-backed cross-route admission ledger (one irrevocable owner file per
13427
+ * appId/messageId).
13428
+ *
13429
+ * Lock ownership: the caller must already hold the shared state-directory lifetime
13430
+ * lock (`openTerminalMessageClaims`) for this directory and keep it for the
13431
+ * store's lifetime; this factory never acquires or releases a lock itself and
13432
+ * never opens a connection. Crash recovery is manual: lock files and
13433
+ * owner files are preserved; a lock is removed only after its owner is verified
13434
+ * stopped, never by PID/age-based theft.
13435
+ */
13436
+ async function openFileRouteAdmissionLedger(directory, appId) {
13437
+ const admissionsDirectory = join(directory, "route-admissions");
13438
+ await createPrivateChildDirectory(admissionsDirectory, 448, "route admission directory", directory);
13439
+ await syncStateDirectory(directory, "state directory");
13440
+ const ownerPath = (messageId) => join(admissionsDirectory, `${createHash("sha256").update(JSON.stringify([appId, messageId])).digest("hex")}.json`);
13441
+ const syncAdmissionsDirectory = async () => {
13442
+ const handle = await open(admissionsDirectory, "r");
13443
+ try {
13444
+ await handle.sync();
13445
+ } finally {
13446
+ await handle.close();
13447
+ }
13448
+ };
13449
+ const safeStatOwner = async (messageId) => {
13450
+ const path = ownerPath(messageId);
13451
+ assertPrivateOwnerFile(await lstat(path));
13452
+ return path;
13453
+ };
13454
+ const decodeOwner = (raw, messageId) => {
13455
+ let parsed;
13456
+ try {
13457
+ parsed = JSON.parse(raw);
13458
+ } catch {
13459
+ return { kind: "opaque-fence" };
13460
+ }
13461
+ let record;
13462
+ try {
13463
+ const validated = decodeAdmissionRecord(parsed);
13464
+ record = {
13465
+ appId: validated.appId,
13466
+ messageId: validated.messageId,
13467
+ decision: validated.decision,
13468
+ actorId: validated.actorId,
13469
+ chatId: validated.chatId,
13470
+ at: validated.at,
13471
+ ...validated.target === void 0 ? {} : { target: validated.target }
13472
+ };
13473
+ } catch {
13474
+ return { kind: "opaque-fence" };
13475
+ }
13476
+ if (record.appId !== appId || record.messageId !== messageId) throw new Error("Route admission owner belongs to another app or event");
13477
+ return {
13478
+ kind: "record",
13479
+ record
13480
+ };
13481
+ };
13482
+ /**
13483
+ * One safe flow for lookup and EEXIST: an existing owner is stat-validated
13484
+ * first (unsafe files fail before being read or synced); only a valid complete
13485
+ * record is re-synchronized (owner file, admissions directory, closes) before
13486
+ * it is returned, because visibility alone is not durable proof. Partial or
13487
+ * corrupt owners return their opaque fence immediately; failures propagate —
13488
+ * once an owner has been observed by lstat, even ENOENT on the read or during
13489
+ * recovery is a failure, never a downgrade to missing.
13490
+ */
13491
+ /** Shared read/decode/fsync/finally-close plus admissions-dir resync for an already stat-validated owner. */
13492
+ const readAndSyncOwner = async (validatedPath, messageId) => {
13493
+ const lookup = decodeOwner(await readFile(validatedPath, "utf8"), messageId);
13494
+ if (lookup.kind !== "record") return lookup;
13495
+ const handle = await open(validatedPath, "r+");
13496
+ try {
13497
+ await handle.sync();
13498
+ } finally {
13499
+ await handle.close();
13500
+ }
13501
+ await syncAdmissionsDirectory();
13502
+ return lookup;
13503
+ };
13504
+ const loadExisting = async (messageId) => {
13505
+ const path = await safeStatOwner(messageId);
13506
+ return readAndSyncOwner(path, messageId);
13507
+ };
13508
+ return {
13509
+ lookup: (messageId) => Effect.uninterruptible(Effect.tryPromise({
13510
+ try: async () => {
13511
+ let path;
13512
+ try {
13513
+ path = await safeStatOwner(messageId);
13514
+ } catch (error) {
13515
+ if (Schema.is(isErrnoCode$1)(error) && error.code === "ENOENT") return { kind: "missing" };
13516
+ throw error;
13517
+ }
13518
+ return await readAndSyncOwner(path, messageId);
13519
+ },
13520
+ catch: (error) => error
13521
+ })),
13522
+ admit: (input) => Effect.uninterruptible(Effect.tryPromise({
13523
+ try: async () => {
13524
+ const record = decodeAdmissionRecord({
13525
+ ...input,
13526
+ version: 1
13527
+ });
13528
+ if (record.appId !== appId) throw new Error("Route admission record belongs to another app");
13529
+ if (record.decision === "pi" && record.target !== void 0) throw new Error("A pi route admission never carries a Herdr target");
13530
+ const path = ownerPath(record.messageId);
13531
+ let handle;
13532
+ try {
13533
+ handle = await open(path, "wx", 384);
13534
+ } catch (error) {
13535
+ if (Schema.is(isErrnoCode$1)(error) && error.code === "EEXIST") {
13536
+ const lookup = await loadExisting(record.messageId);
13537
+ return lookup.kind === "record" ? {
13538
+ kind: "existing",
13539
+ record: lookup.record
13540
+ } : { kind: "existing-opaque" };
13541
+ }
13542
+ throw error;
13543
+ }
13544
+ try {
13545
+ await handle.writeFile(JSON.stringify({
13546
+ ...record,
13547
+ version: 1
13548
+ }), "utf8");
13549
+ await handle.sync();
13550
+ await syncAdmissionsDirectory();
13551
+ } catch (error) {
13552
+ await handle.close().catch(() => void 0);
13553
+ throw error;
13554
+ }
13555
+ await handle.close();
13556
+ return {
13557
+ kind: "created",
13558
+ record
13559
+ };
13560
+ },
13561
+ catch: (error) => error
13562
+ }))
13563
+ };
13564
+ }
13565
+ //#endregion
13566
+ //#region src/adapters/outbound/persistence/terminal-session/terminal-conversation-states.ts
13567
+ const modeStateSchema = Schema.Union([Schema.Struct({
13568
+ kind: Schema.Literal("selection"),
13569
+ actorId: Schema.String,
13570
+ chatId: Schema.String,
13571
+ since: Schema.Number
13572
+ }), Schema.Struct({
13573
+ kind: Schema.Literal("locked"),
13574
+ actorId: Schema.String,
13575
+ chatId: Schema.String,
13576
+ since: Schema.Number,
13577
+ handle: Schema.String,
13578
+ agent: terminalAgentSchema,
13579
+ stale: Schema.Boolean
13580
+ })]);
13581
+ const snapshotSchema = Schema.Struct({
13582
+ version: Schema.Literal(1),
13583
+ appId: Schema.String,
13584
+ states: Schema.Array(modeStateSchema)
13585
+ });
13586
+ const decodeSnapshot = Schema.decodeUnknownSync(snapshotSchema);
13587
+ const isErrnoCode = Schema.Struct({ code: Schema.String });
13588
+ function assertStateCollection(states) {
13589
+ if (states.length > 64) throw new Error("Conversation state snapshot exceeds capacity");
13590
+ if (new Set(states.map((state) => `${state.actorId}\u0000${state.chatId}`)).size !== states.length) throw new Error("Conversation state snapshot contains duplicate conversations");
13591
+ }
13592
+ function assertPrivateDirectory(info) {
13593
+ if (info.isSymbolicLink() || !info.isDirectory()) throw new Error("Conversation state directory is not a regular directory");
13594
+ if (info.uid !== process.getuid?.() || (info.mode & 63) !== 0) throw new Error("Unsafe conversation state directory permissions or owner");
13595
+ }
13596
+ function assertPrivateFile(info, label) {
13597
+ if (info.isSymbolicLink() || !info.isFile()) throw new Error(`${label} is not a regular file`);
13598
+ if (info.uid !== process.getuid?.() || (info.mode & 127) !== 0) throw new Error(`Unsafe ${label} permissions or owner`);
13599
+ }
13600
+ /**
13601
+ * Versioned per-app conversation mode snapshot (selection/locked states) with a
13602
+ * durable initialization marker.
13603
+ *
13604
+ * Lock ownership: the caller must already hold the shared state-directory lifetime
13605
+ * lock (`openTerminalMessageClaims`) for this directory and keep it for the
13606
+ * store's lifetime; this factory never acquires a lock, releases one, or opens a
13607
+ * connection. Crash recovery is manual: an interrupted
13608
+ * initialization or a snapshot missing after initialization refuses startup
13609
+ * (fail closed) instead of erasing modes; an operator removes a stale lock only
13610
+ * after the previous owner is verified stopped.
13611
+ */
13612
+ async function openTerminalConversationStates(directory, appId) {
13613
+ assertPrivateDirectory(await lstat(directory));
13614
+ const digest = createHash("sha256").update(appId).digest("hex");
13615
+ const snapshotPath = join(directory, `conversation-states-${digest}.json`);
13616
+ const markerPath = join(directory, `.conversation-states-initialized-${digest}`);
13617
+ const safeFile = async (path, label) => {
13618
+ const info = await lstat(path);
13619
+ if (!info.isFile()) throw new Error(`${label} is not a regular file`);
13620
+ assertPrivateFile(info, label);
13621
+ };
13622
+ const readStates = async () => {
13623
+ await safeFile(snapshotPath, "Conversation state snapshot");
13624
+ let snapshot;
13625
+ try {
13626
+ snapshot = decodeSnapshot(JSON.parse(await readFile(snapshotPath, "utf8")));
13627
+ } catch (error) {
13628
+ throw new Error("Cannot restore conversation states", { cause: error });
13629
+ }
13630
+ if (snapshot.appId !== appId) throw new Error("Conversation state snapshot belongs to another app");
13631
+ const states = snapshot.states;
13632
+ assertStateCollection(states);
13633
+ return states;
13634
+ };
13635
+ const durableSyncDirectory = async () => {
13636
+ const handle = await open(directory, "r");
13637
+ try {
13638
+ await handle.sync();
13639
+ } finally {
13640
+ await handle.close();
13641
+ }
13642
+ };
13643
+ const createMarker = async () => {
13644
+ const marker = await open(markerPath, "wx", 384);
13645
+ try {
13646
+ await marker.writeFile(JSON.stringify({
13647
+ version: 1,
13648
+ appId
13649
+ }));
13650
+ await marker.sync();
13651
+ await durableSyncDirectory();
13652
+ await marker.close();
13653
+ } catch (error) {
13654
+ await marker.close().catch(() => void 0);
13655
+ throw error;
13656
+ }
13657
+ };
13658
+ const markerStat = await lstat(markerPath).catch((error) => {
13659
+ if (Schema.is(isErrnoCode)(error) && error.code === "ENOENT") return;
13660
+ throw error;
13661
+ });
13662
+ if (markerStat !== void 0) {
13663
+ if (!markerStat.isFile()) throw new Error("Conversation state initialization marker is not a regular file");
13664
+ assertPrivateFile(markerStat, "Conversation state initialization marker");
13665
+ }
13666
+ const snapshotStat = await lstat(snapshotPath).catch((error) => {
13667
+ if (Schema.is(isErrnoCode)(error) && error.code === "ENOENT") return;
13668
+ throw error;
13669
+ });
13670
+ let initial;
13671
+ if (markerStat === void 0 && snapshotStat === void 0) {
13672
+ await createMarker();
13673
+ initial = [];
13674
+ await writeAtomicTextFile(snapshotPath, JSON.stringify({
13675
+ version: 1,
13676
+ appId,
13677
+ states: []
13678
+ }), {
13679
+ mode: 384,
13680
+ durable: true
13681
+ });
13682
+ } else {
13683
+ if (markerStat === void 0) throw new Error("Conversation state initialization is ambiguous; refusing startup");
13684
+ if (snapshotStat === void 0) throw new Error("Conversation state snapshot is missing after initialization");
13685
+ initial = await readStates();
13686
+ await writeAtomicTextFile(snapshotPath, JSON.stringify({
13687
+ version: 1,
13688
+ appId,
13689
+ states: initial
13690
+ }), {
13691
+ mode: 384,
13692
+ durable: true
13693
+ });
13694
+ }
13695
+ return {
13696
+ initial,
13697
+ save: (states) => Effect.uninterruptible(Effect.tryPromise({
13698
+ try: async () => {
13699
+ assertStateCollection(states);
13700
+ const validated = decodeSnapshot({
13701
+ version: 1,
13702
+ appId,
13703
+ states
13704
+ });
13705
+ await writeAtomicTextFile(snapshotPath, JSON.stringify({
13706
+ version: 1,
13707
+ appId,
13708
+ states: validated.states
13709
+ }), {
13710
+ mode: 384,
13711
+ durable: true
13712
+ });
13713
+ },
13714
+ catch: (error) => error
13715
+ }))
13716
+ };
13717
+ }
13718
+ //#endregion
13719
+ //#region src/bootstrap/feishu/embedded-herdr-route.ts
13720
+ const herdrRouteConfigSchema = embeddedHerdrRouteSchema;
13721
+ /**
13722
+ * Parses RIVUS_HERDR_ROUTE_CONFIG and fails the assembly unless exactly one
13723
+ * enabled endpoint resolves to the configured appId. A missing variable keeps
13724
+ * the deployment entirely on the Pi path.
13725
+ */
13726
+ async function resolveHerdrRouteConfig(env, manifest) {
13727
+ const configPath = env.RIVUS_HERDR_ROUTE_CONFIG?.trim();
13728
+ if (!configPath) return;
13729
+ if (!isAbsolute(configPath)) throw new Error(`RIVUS_HERDR_ROUTE_CONFIG must be an absolute path: ${configPath}`);
13730
+ let config;
13731
+ try {
13732
+ const raw = await readFile(configPath, "utf8");
13733
+ config = Schema.decodeUnknownSync(herdrRouteConfigSchema)(JSON.parse(raw));
13734
+ } catch (error) {
13735
+ throw new Error(`Invalid RIVUS_HERDR_ROUTE_CONFIG at ${configPath}`, { cause: error });
13736
+ }
13737
+ if ((config.sessions?.length ?? 0) > 0 === (config.discoverSessionsIn !== void 0)) throw new Error("RIVUS_HERDR_ROUTE_CONFIG requires exactly one of sessions or discoverSessionsIn");
13738
+ for (const [actor, chat] of Object.entries(config.menuChats ?? {})) if (!config.allowedActors.includes(actor) || !config.allowedChats.includes(chat)) throw new Error("Herdr route menu destinations must be allowlisted");
13739
+ const matched = manifest.endpoints.filter((endpoint) => {
13740
+ if (!endpoint.enabled) return false;
13741
+ try {
13742
+ return resolveFeishuEndpointCredentials(endpoint.credentialRef, env).appId === config.appId;
13743
+ } catch {
13744
+ return false;
13745
+ }
13746
+ });
13747
+ if (matched.length !== 1) throw new Error(`RIVUS_HERDR_ROUTE_CONFIG appId must match exactly one enabled endpoint (matched ${matched.length})`);
13748
+ return {
13749
+ appId: config.appId,
13750
+ endpointId: matched[0].id,
13751
+ allowedActors: config.allowedActors,
13752
+ allowedChats: config.allowedChats,
13753
+ stateDirectory: config.stateDirectory,
13754
+ writable: config.writable === true,
13755
+ ...config.sessions === void 0 ? {} : { sessions: config.sessions },
13756
+ ...config.discoverSessionsIn === void 0 ? {} : { discoverSessionsIn: config.discoverSessionsIn },
13757
+ ...config.menuChats === void 0 ? {} : { menuChats: config.menuChats },
13758
+ ...config.fontFiles === void 0 ? {} : { fontFiles: config.fontFiles }
13759
+ };
13760
+ }
13761
+ /**
13762
+ * Startup-built pre-upgrade Pi fence: the same inbox file decoded with the same
13763
+ * codec the live store uses, reduced to received ids of any status. Corruption
13764
+ * fails the endpoint assembly before any WebSocket starts.
13765
+ */
13766
+ async function buildLegacyPiInboxIndex(filePath) {
13767
+ const deliveries = decodeInboundDeliverySnapshots(await readPersistenceFile(filePath), {
13768
+ acceptLegacySameAttemptClaims: true,
13769
+ label: "Feishu inbox",
13770
+ payload: feishuInboundDeliveryPayloadCodec
13771
+ });
13772
+ return new Set(deliveries.map((delivery) => delivery.id));
13773
+ }
13774
+ /**
13775
+ * Opens every embedded Herdr store (claims lock, bindings, mode snapshot,
13776
+ * owner ledger) plus the connection-less runtime and router. The opener
13777
+ * Promise is allocated lazily inside the Effect and joined on interruption:
13778
+ * a cancelled hand still waits for the stores it may have acquired and
13779
+ * releases them instead of leaving an ownerless lock holder behind.
13780
+ */
13781
+ function openEmbeddedHerdrRoute(input) {
13782
+ return Effect.suspend(() => {
13783
+ const opener = buildEmbeddedHerdrRoute(input);
13784
+ return Effect.onInterrupt(Effect.tryPromise({
13785
+ catch: (error) => error,
13786
+ try: () => opener
13787
+ }), () => Effect.promise(() => opener.then((opened) => opened.runtime.stop().then(() => void 0, () => void 0), () => void 0)));
13788
+ });
13789
+ }
13790
+ async function buildEmbeddedHerdrRoute(input) {
13791
+ const { herdr } = input;
13792
+ return (async () => {
13793
+ const legacyPiIds = await buildLegacyPiInboxIndex(join(input.endpointStateDirectory, "feishu-inbox.jsonl"));
13794
+ const claims = await openTerminalMessageClaims(herdr.stateDirectory, herdr.appId);
13795
+ const closeClaims = () => claims.close().catch(() => void 0);
13796
+ let bindings;
13797
+ let states;
13798
+ let ledger;
13799
+ let stopState;
13800
+ let runtime;
13801
+ let router;
13802
+ try {
13803
+ bindings = await openTerminalBindingsStore(herdr.stateDirectory, herdr.appId);
13804
+ states = await openTerminalConversationStates(herdr.stateDirectory, herdr.appId);
13805
+ ledger = await openFileRouteAdmissionLedger(herdr.stateDirectory, herdr.appId);
13806
+ stopState = { stopped: false };
13807
+ runtime = createHerdrFeishuBridgeRuntime({
13808
+ sessions: createHerdrSocketClient(herdr.sessions ?? [], 5e3, herdr.discoverSessionsIn),
13809
+ actors: herdr.allowedActors,
13810
+ chats: herdr.allowedChats,
13811
+ writable: herdr.writable,
13812
+ claim: claims.claim,
13813
+ bindings,
13814
+ conversation: {
13815
+ epoch: randomUUID(),
13816
+ states
13817
+ },
13818
+ delivery: createFeishuTerminalDelivery(new Lark.Client({
13819
+ appId: herdr.appId,
13820
+ appSecret: input.appSecret,
13821
+ domain: input.baseUrl,
13822
+ loggerLevel: Lark.LoggerLevel.error
13823
+ }), { isStopped: () => stopState.stopped }),
13824
+ ...herdr.fontFiles === void 0 ? {} : { fontFiles: herdr.fontFiles },
13825
+ stopState,
13826
+ closeStores: () => claims.close()
13827
+ });
13828
+ router = createFeishuConversationRouter({
13829
+ appId: herdr.appId,
13830
+ bridge: runtime.bridge,
13831
+ ledger,
13832
+ actors: herdr.allowedActors,
13833
+ chats: herdr.allowedChats,
13834
+ legacyOwnerOf: (messageId) => Effect.gen(function* () {
13835
+ const claimed = yield* claims.hasClaim(messageId);
13836
+ const pi = legacyPiIds.has(messageId);
13837
+ if (pi && claimed) return "conflict";
13838
+ if (pi) return "pi";
13839
+ return claimed ? "herdr" : "none";
13840
+ }),
13841
+ groupPolicy: input.groupPolicy,
13842
+ botOpenId: input.botOpenId,
13843
+ target: runtime
13844
+ });
13845
+ } catch (error) {
13846
+ await closeClaims();
13847
+ throw error;
13848
+ }
13849
+ return {
13850
+ runtime,
13851
+ routedDispatcher: () => createRoutedFeishuEventDispatcher(new Lark.EventDispatcher({}), {
13852
+ message: router.routeMessage,
13853
+ card: router.routeCardAction,
13854
+ menu: router.routeMenuEvent
13855
+ }, herdr.menuChats ?? {})
13856
+ };
13857
+ })();
13858
+ }
13859
+ function wrapEmbeddedHerdrEndpointLifecycle(endpoint, runtime) {
13860
+ return {
13861
+ running: () => endpoint.running(),
13862
+ start: () => Effect.uninterruptibleMask((restore) => Effect.gen(function* () {
13863
+ yield* Effect.tryPromise({
13864
+ catch: (error) => error,
13865
+ try: () => runtime.start()
13866
+ });
13867
+ const startExit = yield* Effect.exit(restore(Effect.suspend(() => endpoint.start())));
13868
+ if (Exit.isFailure(startExit)) {
13869
+ yield* Effect.exit(Effect.tryPromise({
13870
+ catch: () => void 0,
13871
+ try: () => runtime.stop()
13872
+ }));
13873
+ return yield* Effect.failCause(startExit.cause);
13874
+ }
13875
+ })),
13876
+ stop: () => Effect.uninterruptible(Effect.gen(function* () {
13877
+ yield* Effect.sync(() => runtime.stopIntake());
13878
+ const endpointExit = yield* Effect.exit(endpoint.stop());
13879
+ const drainExit = yield* Effect.exit(Effect.tryPromise({
13880
+ catch: (error) => error,
13881
+ try: () => runtime.completeStop()
13882
+ }));
13883
+ if (Exit.isFailure(endpointExit)) return yield* Effect.failCause(endpointExit.cause);
13884
+ if (Exit.isFailure(drainExit)) return yield* Effect.failCause(drainExit.cause);
13885
+ }))
13886
+ };
13887
+ }
13888
+ //#endregion
12912
13889
  //#region src/adapters/feishu/run-presentation/feishu-presentation-preparation.ts
12913
13890
  function createFeishuPresentationPreparation(options) {
12914
13891
  return (run) => Effect.gen(function* () {
12915
- const cotPrepared = options.cotPublisher ? yield* options.cotPublisher.prepare(run).pipe(Effect.as(true), Effect.catchAll((error) => options.reportCotError("create", error).pipe(Effect.as(false)))) : false;
13892
+ const cotPrepared = options.cotPublisher ? yield* options.cotPublisher.prepare(run).pipe(Effect.as(true), Effect.catch((error) => options.reportCotError("create", error).pipe(Effect.as(false)))) : false;
12916
13893
  yield* options.prepareCardTarget(run).pipe(Effect.onExit((exit) => {
12917
13894
  if (Exit.isSuccess(exit) || !options.cotPublisher || !cotPrepared) return Effect.void;
12918
- return options.cotPublisher.fail(run.runId).pipe(Effect.catchAllCause((cause) => options.reportCotError("abort", Cause.squash(cause))), Effect.catchAllCause(() => Effect.void));
13895
+ return options.cotPublisher.fail(run.runId).pipe(Effect.catchCause((cause) => options.reportCotError("abort", Cause.squash(cause))), Effect.catchCause(() => Effect.void));
12919
13896
  }));
12920
13897
  });
12921
13898
  }
@@ -13233,7 +14210,7 @@ function replaceChain(chains, replacement) {
13233
14210
  //#region src/adapters/outbound/persistence/card-presentation/in-memory-card-presentation-repository.ts
13234
14211
  function createInMemoryCardPresentationRepository(options = {}) {
13235
14212
  let chains = cloneAndValidate(options.initial ?? []);
13236
- const semaphore = Effect.unsafeMakeSemaphore(1);
14213
+ const semaphore = Semaphore.makeUnsafe(1);
13237
14214
  return {
13238
14215
  snapshot: () => structuredClone(chains),
13239
14216
  transact: (decide) => semaphore.withPermits(1)(Effect.uninterruptible(Effect.gen(function* () {
@@ -13375,7 +14352,7 @@ var FeishuCardTargetRegistryStoreError = class {
13375
14352
  };
13376
14353
  function createJsonFileFeishuCardTargetRegistry(options) {
13377
14354
  let loaded;
13378
- const semaphore = Effect.unsafeMakeSemaphore(1);
14355
+ const semaphore = Semaphore.makeUnsafe(1);
13379
14356
  const ready = () => {
13380
14357
  if (loaded) return Effect.succeed(loaded);
13381
14358
  return semaphore.withPermits(1)(loaded ? Effect.succeed(loaded) : openJsonCardPresentationRepository(options).pipe(Effect.map(createCardPresentationService), Effect.mapError(toRegistryStoreError), Effect.tap((service) => Effect.sync(() => {
@@ -14254,7 +15231,7 @@ function createEffectSessionScheduler(options) {
14254
15231
  const readySet = /* @__PURE__ */ new Set();
14255
15232
  const activeSessions = /* @__PURE__ */ new Set();
14256
15233
  const inFlight = /* @__PURE__ */ new Map();
14257
- const serial = Effect.unsafeMakeSemaphore(1);
15234
+ const serial = Semaphore.makeUnsafe(1);
14258
15235
  let queuedRuns = 0;
14259
15236
  let disposed = false;
14260
15237
  const markReady = (sessionKey) => {
@@ -14275,7 +15252,7 @@ function createEffectSessionScheduler(options) {
14275
15252
  readySet.delete(sessionKey);
14276
15253
  const readyIndex = readySessions.indexOf(sessionKey);
14277
15254
  if (readyIndex >= 0) readySessions.splice(readyIndex, 1);
14278
- })).pipe(Effect.zipRight(Deferred.succeed(scheduled.interruption, void 0)), Effect.asVoid);
15255
+ })).pipe(Effect.andThen(Deferred.succeed(scheduled.interruption, void 0)), Effect.asVoid);
14279
15256
  const pump = () => Effect.gen(function* () {
14280
15257
  const selected = yield* serial.withPermits(1)(Effect.gen(function* () {
14281
15258
  const runs = [];
@@ -14300,11 +15277,11 @@ function createEffectSessionScheduler(options) {
14300
15277
  }
14301
15278
  return runs;
14302
15279
  }));
14303
- yield* Effect.forEach(selected, ({ cacheEntry, completion, scheduled }) => Effect.forkDaemon(Effect.raceFirst(runtimes.start(cacheEntry, () => options.createRuntime(scheduled.input.sessionKey)).pipe(Effect.zipRight(Deferred.await(cacheEntry.deferred)), Effect.flatMap((runtime) => serial.withPermits(1)(Effect.sync(() => !disposed && inFlight.get(scheduled.input.sessionKey)?.scheduled === scheduled)).pipe(Effect.flatMap((mayRun) => mayRun ? runtime.run(scheduled.input) : Effect.fail(new SessionRuntimeSchedulerDisposed("session scheduler was disposed")))))), Deferred.await(scheduled.interruption).pipe(Effect.zipRight(Effect.interrupt))).pipe(Effect.interruptible, Effect.exit, Effect.tap((exit) => Deferred.done(scheduled.deferred, exit)), Effect.ensuring(serial.withPermits(1)(Effect.sync(() => {
15280
+ yield* Effect.forEach(selected, ({ cacheEntry, completion, scheduled }) => Effect.forkDetach(Effect.raceFirst(runtimes.start(cacheEntry, () => options.createRuntime(scheduled.input.sessionKey)).pipe(Effect.andThen(Deferred.await(cacheEntry.deferred)), Effect.flatMap((runtime) => serial.withPermits(1)(Effect.sync(() => !disposed && inFlight.get(scheduled.input.sessionKey)?.scheduled === scheduled)).pipe(Effect.flatMap((mayRun) => mayRun ? runtime.run(scheduled.input) : Effect.fail(new SessionRuntimeSchedulerDisposed("session scheduler was disposed")))))), Deferred.await(scheduled.interruption).pipe(Effect.andThen(Effect.interrupt))).pipe(Effect.interruptible, Effect.exit, Effect.tap((exit) => Deferred.done(scheduled.deferred, exit)), Effect.ensuring(serial.withPermits(1)(Effect.sync(() => {
14304
15281
  activeSessions.delete(scheduled.input.sessionKey);
14305
15282
  if (inFlight.get(scheduled.input.sessionKey)?.scheduled === scheduled) inFlight.delete(scheduled.input.sessionKey);
14306
15283
  if ((queues.get(scheduled.input.sessionKey)?.length ?? 0) > 0) markReady(scheduled.input.sessionKey);
14307
- })).pipe(Effect.zipRight(Deferred.succeed(completion, void 0)), Effect.asVoid)), Effect.flatMap(() => pump()))), { discard: true });
15284
+ })).pipe(Effect.andThen(Deferred.succeed(completion, void 0)), Effect.asVoid)), Effect.flatMap(() => pump())), { uninterruptible: "inherit" }), { discard: true });
14308
15285
  });
14309
15286
  const existingRuntime = (sessionKey) => Effect.gen(function* () {
14310
15287
  const selected = yield* runtimes.getExisting(sessionKey);
@@ -14757,19 +15734,19 @@ function createAutomationScheduler(options) {
14757
15734
  const mandate = mandates.activate(options.binding);
14758
15735
  const clock = options.clock ?? systemClock;
14759
15736
  const schedule = createDailyAutomationSchedule(options.binding.schedule, options.binding.timeZone, clock.now());
14760
- const execution = Effect.unsafeMakeSemaphore(1);
14761
- const lifecycle = Effect.unsafeMakeSemaphore(1);
15737
+ const execution = Semaphore.makeUnsafe(1);
15738
+ const lifecycle = Semaphore.makeUnsafe(1);
14762
15739
  let loopFiber;
14763
15740
  let running = false;
14764
15741
  const runDue = (at) => execution.withPermits(1)(executeDue(options, schedule, at, (occurrence) => mandates.claim(mandate.id, occurrence, options.binding.servicePrincipalId)));
14765
15742
  const start = () => lifecycle.withPermits(1)(Effect.suspend(() => {
14766
15743
  if (running) return Effect.void;
14767
15744
  running = true;
14768
- const loop = runScheduleLoop(options, schedule, clock, runDue).pipe(Effect.catchAll((error) => notifyError(options.onError, error)), Effect.ensuring(Effect.sync(() => {
15745
+ const loop = runScheduleLoop(options, schedule, clock, runDue).pipe(Effect.catch((error) => notifyError(options.onError, error)), Effect.ensuring(Effect.sync(() => {
14769
15746
  running = false;
14770
15747
  loopFiber = void 0;
14771
15748
  })));
14772
- return Effect.forkDaemon(loop).pipe(Effect.tap((fiber) => Effect.sync(() => {
15749
+ return Effect.forkDetach(loop, { uninterruptible: "inherit" }).pipe(Effect.tap((fiber) => Effect.sync(() => {
14773
15750
  if (running) loopFiber = fiber;
14774
15751
  })), Effect.asVoid);
14775
15752
  }));
@@ -14830,7 +15807,7 @@ function executeDue(options, schedule, at, claim) {
14830
15807
  body: result.body,
14831
15808
  ...result.presentation === void 0 ? {} : { presentation: result.presentation },
14832
15809
  runId: result.runId
14833
- }))))), Effect.catchAll((error) => options.repository.save(running.fail(automationFailureMessage(error))).pipe(Effect.zipRight(Effect.fail(error)))));
15810
+ }))))), Effect.catch((error) => options.repository.save(running.fail(automationFailureMessage(error))).pipe(Effect.andThen(Effect.fail(error)))));
14834
15811
  }
14835
15812
  if (record.status === "suppressed") return record;
14836
15813
  const snapshot = record.toSnapshot();
@@ -14864,7 +15841,7 @@ function automationFailureMessage(error) {
14864
15841
  return message.trim() === "" ? "Unknown Automation run failure" : message;
14865
15842
  }
14866
15843
  function notifyError(onError, error) {
14867
- return (onError?.(error) ?? Effect.void).pipe(Effect.catchAll(() => Effect.void));
15844
+ return (onError?.(error) ?? Effect.void).pipe(Effect.catch(() => Effect.void));
14868
15845
  }
14869
15846
  const systemClock = {
14870
15847
  now: () => /* @__PURE__ */ new Date(),
@@ -15146,112 +16123,6 @@ function append(filePath, record) {
15146
16123
  });
15147
16124
  }
15148
16125
  //#endregion
15149
- //#region src/adapters/feishu/inbound-delivery/jsonl-inbound-delivery-store.ts
15150
- function openJsonlInboundDeliveryStore(options) {
15151
- const codecOptions = {
15152
- ...options.acceptLegacySameAttemptClaims === void 0 ? {} : { acceptLegacySameAttemptClaims: options.acceptLegacySameAttemptClaims },
15153
- label: options.label ?? "Inbound Delivery",
15154
- payload: options.payload
15155
- };
15156
- return Effect.gen(function* () {
15157
- const raw = yield* Effect.tryPromise({
15158
- try: () => readPersistenceFile(options.filePath),
15159
- catch: (cause) => cause
15160
- });
15161
- return createInMemoryDurableQueueStore({
15162
- initial: yield* Effect.try({
15163
- try: () => decodeInboundDeliverySnapshots(raw, codecOptions),
15164
- catch: (cause) => cause
15165
- }),
15166
- persist: (delivery) => appendInboundDeliverySnapshot(options.filePath, delivery)
15167
- });
15168
- });
15169
- }
15170
- function decodeInboundDeliverySnapshots(raw, options) {
15171
- if (raw === void 0) return [];
15172
- const latest = /* @__PURE__ */ new Map();
15173
- for (const [index, line] of raw.split("\n").entries()) {
15174
- if (line.trim().length === 0) continue;
15175
- let parsed;
15176
- try {
15177
- parsed = JSON.parse(line);
15178
- } catch {
15179
- throw new Error(`invalid ${options.label} snapshot at line ${index + 1}`);
15180
- }
15181
- if (!isEnvelope(parsed)) throw new Error(`invalid ${options.label} snapshot at line ${index + 1}`);
15182
- let delivery;
15183
- try {
15184
- delivery = decodeDelivery(parsed.delivery, options.payload);
15185
- } catch {
15186
- throw new Error(`invalid ${options.label} snapshot at line ${index + 1}`);
15187
- }
15188
- const previous = latest.get(delivery.id);
15189
- if (delivery.revision !== (previous?.revision ?? 0) + 1) throw new Error(`invalid ${options.label} revision at line ${index + 1}`);
15190
- try {
15191
- if (previous) {
15192
- if (!isValidPersistedTransition(previous, delivery, options.acceptLegacySameAttemptClaims ?? false)) throw new Error("transition");
15193
- } else assertNewInboundDelivery(delivery);
15194
- } catch {
15195
- throw new Error(`invalid ${options.label} transition at line ${index + 1}`);
15196
- }
15197
- latest.set(delivery.id, delivery);
15198
- }
15199
- return [...latest.values()];
15200
- }
15201
- function encodeInboundDeliverySnapshot(delivery) {
15202
- const snapshot = readInboundDeliverySnapshot(delivery);
15203
- const persisted = {
15204
- acceptedAt: snapshot.acceptedAt,
15205
- attempts: snapshot.attempts,
15206
- id: snapshot.id,
15207
- laneKey: snapshot.laneKey,
15208
- ...snapshot.metadata === void 0 ? {} : { options: snapshot.metadata },
15209
- payload: snapshot.payload,
15210
- ...snapshot.recovery === void 0 ? {} : { recovery: snapshot.recovery },
15211
- revision: snapshot.revision,
15212
- state: snapshot.state
15213
- };
15214
- return JSON.stringify({
15215
- delivery: persisted,
15216
- version: 1
15217
- });
15218
- }
15219
- function decodeDelivery(value, payloadCodec) {
15220
- if (!isRecord(value) || typeof value.acceptedAt !== "string" || typeof value.attempts !== "number" || typeof value.id !== "string" || typeof value.laneKey !== "string" || typeof value.revision !== "number") throw new Error("invalid Inbound Delivery snapshot");
15221
- const metadata = payloadCodec.decodeMetadata(value.options);
15222
- return readInboundDeliverySnapshot({
15223
- acceptedAt: value.acceptedAt,
15224
- attempts: value.attempts,
15225
- id: value.id,
15226
- laneKey: value.laneKey,
15227
- ...metadata === void 0 ? {} : { metadata },
15228
- payload: payloadCodec.decodePayload(value.payload),
15229
- ...value.recovery === void 0 ? {} : { recovery: value.recovery },
15230
- revision: value.revision,
15231
- state: value.state
15232
- });
15233
- }
15234
- function isEnvelope(value) {
15235
- return isRecord(value) && value.version === 1 && "delivery" in value;
15236
- }
15237
- function isValidPersistedTransition(previous, next, acceptLegacySameAttemptClaims) {
15238
- if (isValidInboundDeliveryTransition(previous, next)) return true;
15239
- if (!acceptLegacySameAttemptClaims || previous.state.status !== "pending" || next.state.status !== "leased" || next.attempts !== previous.attempts) return false;
15240
- return isValidInboundDeliveryTransition(previous, {
15241
- ...next,
15242
- attempts: next.attempts + 1
15243
- });
15244
- }
15245
- function appendInboundDeliverySnapshot(filePath, delivery) {
15246
- return Effect.tryPromise({
15247
- try: async () => {
15248
- await mkdir(dirname(filePath), { recursive: true });
15249
- await appendFile(filePath, `${encodeInboundDeliverySnapshot(delivery)}\n`, "utf8");
15250
- },
15251
- catch: (cause) => cause
15252
- });
15253
- }
15254
- //#endregion
15255
16126
  //#region src/adapters/feishu/conversation-session/json-feishu-session-store.ts
15256
16127
  function openJsonFeishuSessionStore(options) {
15257
16128
  return Effect.tryPromise({
@@ -15752,7 +16623,7 @@ function listChildDirectories(parent) {
15752
16623
  return Effect.tryPromise({
15753
16624
  try: () => readdir(parent, { withFileTypes: true }),
15754
16625
  catch: (error) => error
15755
- }).pipe(Effect.map((entries) => entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort((left, right) => left.localeCompare(right))), Effect.catchAll((error) => error instanceof Error && "code" in error && error.code === "ENOENT" ? Effect.succeed([]) : Effect.fail(error)));
16626
+ }).pipe(Effect.map((entries) => entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort((left, right) => left.localeCompare(right))), Effect.catch((error) => error instanceof Error && "code" in error && error.code === "ENOENT" ? Effect.succeed([]) : Effect.fail(error)));
15756
16627
  }
15757
16628
  //#endregion
15758
16629
  //#region src/bootstrap/pi/pi-session-options.ts
@@ -15852,7 +16723,7 @@ function createBackgroundSessionDeliveryQueue(options) {
15852
16723
  return {
15853
16724
  drain: () => Effect.gen(function* () {
15854
16725
  const records = yield* fromPromise$1(() => options.deliveries.pending());
15855
- yield* Effect.forEach(records, (record) => fromPromise$1(() => options.deliver(record)).pipe(Effect.flatMap(({ providerMessageId }) => fromPromise$1(() => options.deliveries.markDelivered(record.deliveryId, providerMessageId))), Effect.tap((delivered) => delivered ? Effect.sync(options.onDelivered) : Effect.void), Effect.catchAll((error) => options.onError === void 0 ? Effect.void : Effect.sync(() => options.onError?.(error))), Effect.asVoid), { discard: true });
16726
+ yield* Effect.forEach(records, (record) => fromPromise$1(() => options.deliver(record)).pipe(Effect.flatMap(({ providerMessageId }) => fromPromise$1(() => options.deliveries.markDelivered(record.deliveryId, providerMessageId))), Effect.tap((delivered) => delivered ? Effect.sync(options.onDelivered) : Effect.void), Effect.catch((error) => options.onError === void 0 ? Effect.void : Effect.sync(() => options.onError?.(error))), Effect.asVoid), { discard: true });
15856
16727
  }),
15857
16728
  enqueue: (sessionId, kind, text, session) => {
15858
16729
  if (text.trim() === "") return Effect.void;
@@ -15934,7 +16805,7 @@ function createBackgroundSessionSupervisor(options) {
15934
16805
  const owner = `supervisor:${options.createId()}`;
15935
16806
  const inFlight = /* @__PURE__ */ new Map();
15936
16807
  const stepControllers = /* @__PURE__ */ new Map();
15937
- const supervision = Effect.unsafeMakeSemaphore(1);
16808
+ const supervision = Semaphore.makeUnsafe(1);
15938
16809
  const counters = {
15939
16810
  completed: 0,
15940
16811
  deliveriesDelivered: 0,
@@ -15956,7 +16827,7 @@ function createBackgroundSessionSupervisor(options) {
15956
16827
  },
15957
16828
  ...options.onError === void 0 ? {} : { onError: options.onError }
15958
16829
  });
15959
- const reportError = (error) => options.onError === void 0 ? Effect.void : Effect.sync(() => options.onError?.(error)).pipe(Effect.catchAllCause(() => Effect.void));
16830
+ const reportError = (error) => options.onError === void 0 ? Effect.void : Effect.sync(() => options.onError?.(error)).pipe(Effect.catchCause(() => Effect.void));
15960
16831
  const repositoryGet = (sessionId) => fromPromise(() => options.repository.get(sessionId));
15961
16832
  const repositoryList = () => fromPromise(() => options.repository.list());
15962
16833
  const persistTransition = (input) => Effect.uninterruptible(fromPromise(() => commitBackgroundSessionTransition({
@@ -16084,7 +16955,7 @@ function createBackgroundSessionSupervisor(options) {
16084
16955
  wakeText: buildInterruptedBackgroundSessionWakeText(state)
16085
16956
  })
16086
16957
  });
16087
- }).pipe(Effect.catchAll((error) => reportError(error)), Effect.asVoid);
16958
+ }).pipe(Effect.catch((error) => reportError(error)), Effect.asVoid);
16088
16959
  const renewLeaseOnce = (sessionId, lease, controller) => Effect.gen(function* () {
16089
16960
  const current = yield* repositoryGet(sessionId);
16090
16961
  if (!current || current.lease === void 0 || current.lease.owner !== owner || current.lease.epoch !== lease.epoch) {
@@ -16111,8 +16982,8 @@ function createBackgroundSessionSupervisor(options) {
16111
16982
  return true;
16112
16983
  });
16113
16984
  const renewLease = (sessionId, lease, controller) => {
16114
- const cycle = Effect.suspend(() => options.sleep(options.config.leaseRenewalIntervalMs).pipe(Effect.flatMap(() => renewLeaseOnce(sessionId, lease, controller)), Effect.flatMap((renewed) => renewed ? cycle : Effect.void), Effect.catchAllCause((cause) => {
16115
- if (Cause.isInterruptedOnly(cause)) return Effect.interrupt;
16985
+ const cycle = Effect.suspend(() => options.sleep(options.config.leaseRenewalIntervalMs).pipe(Effect.flatMap(() => renewLeaseOnce(sessionId, lease, controller)), Effect.flatMap((renewed) => renewed ? cycle : Effect.void), Effect.catchCause((cause) => {
16986
+ if (Cause.hasInterruptsOnly(cause)) return Effect.interrupt;
16116
16987
  controller.abort(/* @__PURE__ */ new Error(`background session lease renewal failed: ${backgroundSessionErrorMessage(Cause.squash(cause))}`));
16117
16988
  return Effect.void;
16118
16989
  })));
@@ -16134,7 +17005,7 @@ function createBackgroundSessionSupervisor(options) {
16134
17005
  wakeText: `步骤结果提交失败(${backgroundSessionErrorMessage(error)}),将重试`
16135
17006
  })
16136
17007
  });
16137
- }).pipe(Effect.catchAll((commitError) => reportError(commitError)), Effect.asVoid);
17008
+ }).pipe(Effect.catch((commitError) => reportError(commitError)), Effect.asVoid);
16138
17009
  const commitStep = (claimed, outcome, lease) => Effect.gen(function* () {
16139
17010
  const now = options.clock.now();
16140
17011
  const current = yield* repositoryGet(claimed.sessionId);
@@ -16239,7 +17110,7 @@ function createBackgroundSessionSupervisor(options) {
16239
17110
  const controller = new AbortController();
16240
17111
  stepControllers.set(session.sessionId, controller);
16241
17112
  yield* Effect.gen(function* () {
16242
- const outcome = yield* Effect.acquireUseRelease(Effect.fork(renewLease(session.sessionId, lease, controller).pipe(Effect.interruptible)), () => options.runStep({
17113
+ const outcome = yield* Effect.acquireUseRelease(Effect.forkChild(renewLease(session.sessionId, lease, controller), { uninterruptible: false }), () => options.runStep({
16243
17114
  session: claimed,
16244
17115
  signal: controller.signal,
16245
17116
  wakeText
@@ -16253,16 +17124,16 @@ function createBackgroundSessionSupervisor(options) {
16253
17124
  result
16254
17125
  })
16255
17126
  })), (fiber) => Fiber.interrupt(fiber).pipe(Effect.asVoid));
16256
- yield* Effect.uninterruptible(commitStep(claimed, outcome, lease).pipe(Effect.catchAll((error) => reportError(error).pipe(Effect.flatMap(() => requeueAfterFailedCommit(session.sessionId, lease, error))))));
17127
+ yield* Effect.uninterruptible(commitStep(claimed, outcome, lease).pipe(Effect.catch((error) => reportError(error).pipe(Effect.flatMap(() => requeueAfterFailedCommit(session.sessionId, lease, error))))));
16257
17128
  }).pipe(Effect.onInterrupt(() => Effect.sync(() => controller.abort(/* @__PURE__ */ new Error("background session supervisor is stopping"))).pipe(Effect.flatMap(() => requeueAfterInterruptedFiber(session.sessionId, lease)))), Effect.ensuring(Effect.sync(() => {
16258
17129
  stepControllers.delete(session.sessionId);
16259
17130
  })));
16260
17131
  });
16261
17132
  const startStep = (session) => Effect.gen(function* () {
16262
17133
  const gate = yield* Deferred.make();
16263
- const fiber = yield* Effect.forkDaemon(Deferred.await(gate).pipe(Effect.flatMap(() => runStep(session)), Effect.catchAllCause((cause) => Cause.isInterruptedOnly(cause) ? Effect.void : reportError(Cause.squash(cause))), Effect.ensuring(Effect.sync(() => {
17134
+ const fiber = yield* Effect.forkDetach(Deferred.await(gate).pipe(Effect.flatMap(() => runStep(session)), Effect.catchCause((cause) => Cause.hasInterruptsOnly(cause) ? Effect.void : reportError(Cause.squash(cause))), Effect.ensuring(Effect.sync(() => {
16264
17135
  inFlight.delete(session.sessionId);
16265
- }))));
17136
+ }))), { uninterruptible: "inherit" });
16266
17137
  inFlight.set(session.sessionId, fiber);
16267
17138
  yield* Deferred.succeed(gate, void 0);
16268
17139
  }).pipe(Effect.uninterruptible);
@@ -16338,7 +17209,7 @@ function createHumanInteractionService(options) {
16338
17209
  }) : (ms) => Effect.sleep(ms);
16339
17210
  const persistAndPresent = (interaction, existing) => Effect.gen(function* () {
16340
17211
  if (Date.parse(options.clock.now()) >= Date.parse(interaction.expiresAt)) return yield* Effect.fail(/* @__PURE__ */ new Error(`human interaction expired before presentation: ${interaction.id}`));
16341
- const persisted = existing ? existing : yield* options.repository.create(interaction).pipe(Effect.as(interaction), Effect.catchAll((createError) => options.repository.get(interaction.id).pipe(Effect.flatMap((concurrent) => concurrent ? Effect.succeed(concurrent) : Effect.fail(createError)))));
17212
+ const persisted = existing ? existing : yield* options.repository.create(interaction).pipe(Effect.as(interaction), Effect.catch((createError) => options.repository.get(interaction.id).pipe(Effect.flatMap((concurrent) => concurrent ? Effect.succeed(concurrent) : Effect.fail(createError)))));
16342
17213
  if (!persisted.isSameRequest(interaction)) return yield* Effect.fail(/* @__PURE__ */ new Error(`human interaction id is already bound to another request: ${interaction.id}`));
16343
17214
  yield* options.presenter.present(persisted);
16344
17215
  return persisted;
@@ -16367,7 +17238,7 @@ function createHumanInteractionService(options) {
16367
17238
  return yield* options.repository.update(input.approvalId, (interaction) => interaction.consume({
16368
17239
  binding: input.binding,
16369
17240
  now: options.clock.now()
16370
- }).state).pipe(Effect.as(true), Effect.catchAll((error) => isTransitionDenied(error) ? Effect.succeed(false) : Effect.fail(error)));
17241
+ }).state).pipe(Effect.as(true), Effect.catch((error) => isTransitionDenied(error) ? Effect.succeed(false) : Effect.fail(error)));
16371
17242
  }),
16372
17243
  get: (interactionId) => options.repository.get(interactionId),
16373
17244
  resolve: (input) => options.repository.update(input.interactionId, (interaction) => interaction.resolve({
@@ -16401,7 +17272,7 @@ function isTransitionDenied(error) {
16401
17272
  return error instanceof Error && error.cause instanceof Error && error.cause.name === "HumanInteractionTransitionDenied";
16402
17273
  }
16403
17274
  function waitForAbort(signal) {
16404
- return Effect.async((resume) => {
17275
+ return Effect.callback((resume) => {
16405
17276
  const onAbort = () => resume(Effect.fail(signal.reason ?? /* @__PURE__ */ new Error("human interaction wait was aborted")));
16406
17277
  if (signal.aborted) {
16407
17278
  onAbort();
@@ -16962,10 +17833,10 @@ async function createRivusDaemonProcess(context) {
16962
17833
  running: () => bootstrap.process.running(),
16963
17834
  start: () => bootstrap.process.start(),
16964
17835
  status: () => bootstrap.status(),
16965
- stop: () => bootstrap.process.stop().pipe(Effect.zipRight(Effect.tryPromise({
17836
+ stop: () => bootstrap.process.stop().pipe(Effect.andThen(Effect.tryPromise({
16966
17837
  try: () => sessionRegistry.disposeAll(),
16967
17838
  catch: (error) => error
16968
- })), Effect.zipRight(Effect.tryPromise({
17839
+ })), Effect.andThen(Effect.tryPromise({
16969
17840
  try: () => assembly.dispose(),
16970
17841
  catch: (error) => error
16971
17842
  })))
@@ -17017,6 +17888,7 @@ async function createRivusDeploymentAdapters(context) {
17017
17888
  manifest
17018
17889
  };
17019
17890
  })));
17891
+ const herdrRoute = await resolveHerdrRouteConfig(context.env, manifest);
17020
17892
  const memoryTenantId = context.env.RIVUS_MEMORY_TENANT_ID?.trim() || "local";
17021
17893
  const request = createJsonFetchRequest();
17022
17894
  const attachmentCapabilities = /* @__PURE__ */ new Map();
@@ -17323,6 +18195,15 @@ async function createRivusDeploymentAdapters(context) {
17323
18195
  config
17324
18196
  });
17325
18197
  const prepareCardTarget = (run) => cardRollover.prepareRun(run);
18198
+ const herdr = herdrRoute !== void 0 && input.endpointId === herdrRoute.endpointId ? herdrRoute : void 0;
18199
+ const embedded = herdr ? yield* openEmbeddedHerdrRoute({
18200
+ herdr,
18201
+ appSecret: credentials.appSecret,
18202
+ baseUrl: input.definition.baseUrl,
18203
+ endpointStateDirectory: endpointState,
18204
+ groupPolicy: input.definition.groupPolicy,
18205
+ botOpenId
18206
+ }) : void 0;
17326
18207
  const endpoint = createFeishuDeploymentEndpoint({
17327
18208
  agentId: input.agentId,
17328
18209
  acknowledgeSteering: (messageId) => reactions.add(messageId),
@@ -17333,7 +18214,7 @@ async function createRivusDeploymentAdapters(context) {
17333
18214
  finalizeRun: (runId) => cardRollover.rollover.releaseRun(runId),
17334
18215
  cancel: input.cancel,
17335
18216
  endpointId: input.endpointId,
17336
- eventDispatcher: createLazyFeishuWebSocketEventDispatcher(() => new Lark.EventDispatcher({})),
18217
+ eventDispatcher: embedded ? createLazyFeishuWebSocketEventDispatcher(embedded.routedDispatcher) : createLazyFeishuWebSocketEventDispatcher(() => new Lark.EventDispatcher({})),
17337
18218
  groupPolicy: input.definition.groupPolicy,
17338
18219
  handle: input.handle,
17339
18220
  inboxStore: inbox,
@@ -17354,15 +18235,16 @@ async function createRivusDeploymentAdapters(context) {
17354
18235
  reply: (messageId, text) => replies.reply(messageId, text),
17355
18236
  sessionStore,
17356
18237
  ...input.steer ? { steer: input.steer } : {},
17357
- ...cotPublisher ? { publishRunUpdate: (update) => cotPublisher.publish(update).pipe(Effect.catchAll((error) => reportCotError(input.endpointId, "update", error))) } : {},
18238
+ ...cotPublisher ? { publishRunUpdate: (update) => cotPublisher.publish(update).pipe(Effect.catch((error) => reportCotError(input.endpointId, "update", error))) } : {},
17358
18239
  sessionNamespace: input.definition.sessionNamespace,
17359
18240
  sleep,
17360
18241
  websocketClient: createLazyFeishuWebSocketClient(credentials, input.definition.baseUrl),
17361
18242
  workerConcurrency: 4
17362
18243
  });
17363
18244
  interactionRegistry.register(input.endpointId, interactions);
17364
- return endpoint;
17365
- }).pipe(Effect.catchAllDefect((error) => Effect.fail(error))),
18245
+ if (embedded === void 0) return endpoint;
18246
+ return wrapEmbeddedHerdrEndpointLifecycle(endpoint, embedded.runtime);
18247
+ }).pipe(Effect.catchDefect((error) => Effect.fail(error))),
17366
18248
  createRuntime: (input) => Effect.gen(function* () {
17367
18249
  const instanceState = join(STATE_DIR, "instances", input.instanceId);
17368
18250
  yield* Effect.tryPromise({
@@ -17559,7 +18441,7 @@ async function createRivusDeploymentAdapters(context) {
17559
18441
  concurrency: scheduler.concurrency,
17560
18442
  run: (request) => scheduler.run(request),
17561
18443
  steer: (request) => scheduler.steer(request),
17562
- dispose: () => scheduler.dispose().pipe(Effect.zipRight(Effect.tryPromise({
18444
+ dispose: () => scheduler.dispose().pipe(Effect.andThen(Effect.tryPromise({
17563
18445
  try: () => sessionRegistry.disposeAll(),
17564
18446
  catch: (error) => error
17565
18447
  })), Effect.tap(() => Effect.sync(() => {
@@ -17571,7 +18453,7 @@ async function createRivusDeploymentAdapters(context) {
17571
18453
  }
17572
18454
  })))
17573
18455
  };
17574
- }).pipe(Effect.catchAllDefect((error) => Effect.fail(error)))
18456
+ }).pipe(Effect.catchDefect((error) => Effect.fail(error)))
17575
18457
  };
17576
18458
  } catch (error) {
17577
18459
  await Promise.allSettled([management?.enabled ? management.close() : Promise.resolve(), telemetry?.shutdown()]);