@axiom-lattice/gateway 4.6.1 → 4.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/OrphanRecoveryRuntime-BEFILJMU.mjs +8 -0
  2. package/dist/RecoveryCallerDeps-6SJUYJ5S.mjs +8 -0
  3. package/dist/RecoveryCallerDeps-6SJUYJ5S.mjs.map +1 -0
  4. package/dist/{a2a-standard-PIOKEHPS.mjs → a2a-standard-PBYPNHOA.mjs} +59 -21
  5. package/dist/a2a-standard-PBYPNHOA.mjs.map +1 -0
  6. package/dist/{chunk-2POW27EI.mjs → chunk-7CG57LEF.mjs} +2 -2
  7. package/dist/{chunk-WKNZ7GBC.mjs → chunk-EXPERMZR.mjs} +1 -1
  8. package/dist/chunk-EXPERMZR.mjs.map +1 -0
  9. package/dist/{chunk-52SUWMOX.mjs → chunk-JUA3VJE7.mjs} +3 -2
  10. package/dist/chunk-JUA3VJE7.mjs.map +1 -0
  11. package/dist/chunk-NOXGKYUV.mjs +37 -0
  12. package/dist/chunk-NOXGKYUV.mjs.map +1 -0
  13. package/dist/{chunk-526UYVNQ.mjs → chunk-NPHK4SZ6.mjs} +4 -4
  14. package/dist/{chunk-TJAUXMYJ.mjs → chunk-URXZPI2F.mjs} +8 -1
  15. package/dist/chunk-URXZPI2F.mjs.map +1 -0
  16. package/dist/chunk-ZBTG3T7K.mjs +324 -0
  17. package/dist/chunk-ZBTG3T7K.mjs.map +1 -0
  18. package/dist/embed/{embed-page-AgentDetailSideApp-7NYLPLAX.js → embed-page-AgentDetailSideApp-NTLAFO5C.js} +1 -1
  19. package/dist/embed/{embed-page-AgentGovernanceWorkbench-LNGNSI7X.js → embed-page-AgentGovernanceWorkbench-QPVBOMLH.js} +1 -1
  20. package/dist/embed/{embed-page-MessageGraph-E6DZDT65.js → embed-page-MessageGraph-JAJGMYKH.js} +1 -1
  21. package/dist/embed/{embed-page-ReactChart-3VE7F5VJ.js → embed-page-ReactChart-WUG6TEAZ.js} +1 -1
  22. package/dist/embed/{embed-page-SkillFlow-XMMHEHQL.js → embed-page-SkillFlow-TSBZ4V2P.js} +1 -1
  23. package/dist/embed/{embed-page-TasksSideApp-QM5TZ7EG.js → embed-page-TasksSideApp-TJI22YGD.js} +1 -1
  24. package/dist/embed/{embed-page-TeamWorkspace-E7RFY3ZY.js → embed-page-TeamWorkspace-OJJLN5VI.js} +1 -1
  25. package/dist/embed/{embed-page-TopologyRuntimeView-DLPDUB7Z.js → embed-page-TopologyRuntimeView-ZFU4N6TV.js} +1 -1
  26. package/dist/embed/{embed-page-WorkflowAutomationView-FKSVOHHB.js → embed-page-WorkflowAutomationView-ALXV7VQQ.js} +1 -1
  27. package/dist/embed/{embed-page-chunk-I7PWCKAG.js → embed-page-chunk-DZQBVJ7Z.js} +1 -1
  28. package/dist/embed/{embed-page-chunk-WWTU55RX.js → embed-page-chunk-IPIJ2SG6.js} +1 -1
  29. package/dist/embed/{embed-page-chunk-KN7D66KL.js → embed-page-chunk-N22EGQAJ.js} +1 -1
  30. package/dist/embed/{embed-page-chunk-EXCJ42IB.js → embed-page-chunk-O72XBKQH.js} +2 -2
  31. package/dist/embed/{embed-page-chunk-U276WBDI.js → embed-page-chunk-QMVIL4BJ.js} +190 -185
  32. package/dist/embed/{embed-page-chunk-H6TK3FAR.js → embed-page-chunk-UT3WYBZH.js} +1 -1
  33. package/dist/embed/embed-page-chunk-YPNSOZYW.js +8 -0
  34. package/dist/embed/embed-page.js +1 -1
  35. package/dist/index.d.mts +362 -10
  36. package/dist/index.d.ts +362 -10
  37. package/dist/index.js +1693 -450
  38. package/dist/index.js.map +1 -1
  39. package/dist/index.mjs +714 -154
  40. package/dist/index.mjs.map +1 -1
  41. package/dist/{open-AMEMJOFJ.mjs → open-UVKMONVE.mjs} +10 -9
  42. package/dist/open-UVKMONVE.mjs.map +1 -0
  43. package/dist/{open-exposure-I44Q4M3T.mjs → open-exposure-VU227IO3.mjs} +5 -5
  44. package/dist/{open-files-EJ6O2SGX.mjs → open-files-X3HXVEEM.mjs} +6 -6
  45. package/dist/{open-session-LBTCU3AE.mjs → open-session-2Y7XJJW2.mjs} +6 -6
  46. package/dist/{resources-DXPXAXEZ.mjs → resources-2L3KSUUU.mjs} +2 -2
  47. package/dist/sandboxOpenCredentialProvider-USIU7Z7H.mjs +275 -0
  48. package/dist/sandboxOpenCredentialProvider-USIU7Z7H.mjs.map +1 -0
  49. package/dist/{sandboxProvider-I3P66VXT.mjs → sandboxProvider-DUYWHYJN.mjs} +2 -2
  50. package/dist/sandboxProvider-DUYWHYJN.mjs.map +1 -0
  51. package/package.json +14 -8
  52. package/dist/a2a-standard-PIOKEHPS.mjs.map +0 -1
  53. package/dist/chunk-52SUWMOX.mjs.map +0 -1
  54. package/dist/chunk-TJAUXMYJ.mjs.map +0 -1
  55. package/dist/chunk-WKNZ7GBC.mjs.map +0 -1
  56. package/dist/embed/embed-page-chunk-HEMNMC4X.js +0 -7
  57. package/dist/open-AMEMJOFJ.mjs.map +0 -1
  58. /package/dist/{sandboxProvider-I3P66VXT.mjs.map → OrphanRecoveryRuntime-BEFILJMU.mjs.map} +0 -0
  59. /package/dist/{chunk-2POW27EI.mjs.map → chunk-7CG57LEF.mjs.map} +0 -0
  60. /package/dist/{chunk-526UYVNQ.mjs.map → chunk-NPHK4SZ6.mjs.map} +0 -0
  61. /package/dist/{open-exposure-I44Q4M3T.mjs.map → open-exposure-VU227IO3.mjs.map} +0 -0
  62. /package/dist/{open-files-EJ6O2SGX.mjs.map → open-files-X3HXVEEM.mjs.map} +0 -0
  63. /package/dist/{open-session-LBTCU3AE.mjs.map → open-session-2Y7XJJW2.mjs.map} +0 -0
  64. /package/dist/{resources-DXPXAXEZ.mjs.map → resources-2L3KSUUU.mjs.map} +0 -0
@@ -1,4 +1,4 @@
1
- import{b as f}from"./embed-page-chunk-EXCJ42IB.js";import"./embed-page-chunk-U276WBDI.js";import"./embed-page-chunk-HEMNMC4X.js";import{a as b}from"./embed-page-chunk-MB65DZAX.js";import"./embed-page-chunk-VW3NF5NT.js";import"./embed-page-chunk-5JDQ456X.js";import"./embed-page-chunk-Z7USDLHI.js";import{a as g}from"./embed-page-chunk-O4QK5LWZ.js";import"./embed-page-chunk-4BKXE5ON.js";import"./embed-page-chunk-IX4WLWCY.js";import"./embed-page-chunk-EUOOO2LS.js";import{b as U,e as s}from"./embed-page-chunk-6Y3UYJ6Z.js";var h=U(d=>{"use strict";var E=g();d.createRoot=E.createRoot,d.hydrateRoot=E.hydrateRoot;var ee});var T=s(h()),k=s(g());var y="axiom-web-app-embed";var x=["axiom:hello","axiom:identity","axiom:send","axiom:config"];function w(e,t){return{source:y,version:1,type:e,payload:t}}var N=6,z=40,B=500;function H(e){if(typeof e!="object"||e===null||Array.isArray(e))return!1;let t=e,n=Object.keys(t);return n.length!==2||!n.includes("label")||!n.includes("text")?!1:typeof t.label=="string"&&t.label.length>=1&&t.label.length<=z&&typeof t.text=="string"&&t.text.length>=1&&t.text.length<=B}function Q(e){if(!Array.isArray(e))return[];let t=[];for(let n of e){if(t.length>=N)break;H(n)&&t.push({label:n.label,text:n.text})}return t}function _(e){if(typeof e!="object"||e===null)return null;let t=e,n={};if(typeof t.welcome=="string"&&t.welcome.length>0&&(n.welcome=t.welcome),Array.isArray(t.quicks)){let r=Q(t.quicks);(r.length>0||t.quicks.length===0)&&(n.quicks=r)}return n.welcome===void 0&&n.quicks===void 0?null:n}function R(e,t){if(typeof e!="object"||e===null)return null;let n=e;return n.source!==y||n.version!==1||typeof n.type!="string"||!t.includes(n.type)?null:{type:n.type,payload:n.payload}}var p=s(b()),P=/^[A-Za-z0-9._:@-]{1,128}$/,q="Open this chat from the host page embed to start chatting.",v="axiom-embed-root",C="axiom-embed-style",A=32768,G=`
1
+ import{b as f}from"./embed-page-chunk-O72XBKQH.js";import"./embed-page-chunk-QMVIL4BJ.js";import"./embed-page-chunk-YPNSOZYW.js";import{a as b}from"./embed-page-chunk-MB65DZAX.js";import"./embed-page-chunk-VW3NF5NT.js";import"./embed-page-chunk-5JDQ456X.js";import"./embed-page-chunk-Z7USDLHI.js";import{a as g}from"./embed-page-chunk-O4QK5LWZ.js";import"./embed-page-chunk-4BKXE5ON.js";import"./embed-page-chunk-IX4WLWCY.js";import"./embed-page-chunk-EUOOO2LS.js";import{b as U,e as s}from"./embed-page-chunk-6Y3UYJ6Z.js";var h=U(d=>{"use strict";var E=g();d.createRoot=E.createRoot,d.hydrateRoot=E.hydrateRoot;var ee});var T=s(h()),k=s(g());var y="axiom-web-app-embed";var x=["axiom:hello","axiom:identity","axiom:send","axiom:config"];function w(e,t){return{source:y,version:1,type:e,payload:t}}var N=6,z=40,B=500;function H(e){if(typeof e!="object"||e===null||Array.isArray(e))return!1;let t=e,n=Object.keys(t);return n.length!==2||!n.includes("label")||!n.includes("text")?!1:typeof t.label=="string"&&t.label.length>=1&&t.label.length<=z&&typeof t.text=="string"&&t.text.length>=1&&t.text.length<=B}function Q(e){if(!Array.isArray(e))return[];let t=[];for(let n of e){if(t.length>=N)break;H(n)&&t.push({label:n.label,text:n.text})}return t}function _(e){if(typeof e!="object"||e===null)return null;let t=e,n={};if(typeof t.welcome=="string"&&t.welcome.length>0&&(n.welcome=t.welcome),Array.isArray(t.quicks)){let r=Q(t.quicks);(r.length>0||t.quicks.length===0)&&(n.quicks=r)}return n.welcome===void 0&&n.quicks===void 0?null:n}function R(e,t){if(typeof e!="object"||e===null)return null;let n=e;return n.source!==y||n.version!==1||typeof n.type!="string"||!t.includes(n.type)?null:{type:n.type,payload:n.payload}}var p=s(b()),P=/^[A-Za-z0-9._:@-]{1,128}$/,q="Open this chat from the host page embed to start chatting.",v="axiom-embed-root",C="axiom-embed-style",A=32768,G=`
2
2
  html, body, #${v} { height: 100%; margin: 0; }
3
3
  .axiom-embed-fill { height: 100% !important; min-height: 0 !important; }
4
4
  .axiom-embed-card { display: flex; align-items: center; justify-content: center; height: 100%; margin: 0; font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-size: 14px; color: #64748b; padding: 24px; box-sizing: border-box; text-align: center; }
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as http from 'http';
2
- import { ProjectRoomMessage, ProjectBotMembership, ProjectMembership, TaskItem, ChannelAdapter, InboundMessage, Binding, BindingRegistry, ChannelInstallationStore, DispatchResult, ProjectRoomEventScope, ProjectRoomBusinessEventDraft, ProjectRoomScopedBusinessEvent, ProjectRoomBusinessEvent, ProjectRoomEventSubscription, TaskStore, ProjectRoomStore, ProjectBotMembershipStore, ThreadStore, ProjectRoom, Thread, ProjectStore, LoggerType, PinoFileOptions } from '@axiom-lattice/protocols';
3
- import { TrustedAgentMessageDispatcher, TrustedMessageOptions, TaskLifecycleListener, TaskLifecycleService, IMessageQueueStore, Agent } from '@axiom-lattice/core';
2
+ import { ProjectRoomMessage, ProjectBotMembership, ProjectMembership, TaskItem, ChannelAdapter, InboundMessage, Binding, BindingRegistry, ChannelInstallationStore, DispatchResult, ProjectRoomEventScope, ProjectRoomBusinessEventDraft, ProjectRoomScopedBusinessEvent, ProjectRoomBusinessEvent, ProjectRoomEventSubscription, TaskStore, ProjectRoomStore, ProjectBotMembershipStore, ThreadStore, ProjectRoom, Thread, ProjectStore, TaskWorkItemStore, TenantStore, OutboxStore, LoggerType, PinoFileOptions } from '@axiom-lattice/protocols';
3
+ import { TrustedAgentMessageDispatcher, TrustedMessageOptions, TaskLifecycleListener, TaskLifecycleService, IMessageQueueStore, Agent, TaskRunRegistry } from '@axiom-lattice/core';
4
4
  import * as fastify from 'fastify';
5
5
 
6
6
  /** Public publisher surface for committed Project Room business facts. */
@@ -177,6 +177,7 @@ interface ProjectRoomEventBrokerOptions {
177
177
  maxGlobalEvents?: number;
178
178
  maxGlobalBytes?: number;
179
179
  initialSequence?: number;
180
+ sourceKeyLimit?: number;
180
181
  log?: {
181
182
  warn(message: string, context: Record<string, unknown>): void;
182
183
  };
@@ -195,22 +196,53 @@ declare class ProjectRoomEventBroker {
195
196
  private readonly maxRooms;
196
197
  private readonly maxGlobalEvents;
197
198
  private readonly maxGlobalBytes;
199
+ private readonly sourceKeyLimit;
198
200
  private readonly log;
199
201
  private readonly rooms;
200
202
  private readonly sequenceScopes;
203
+ private readonly seenSourceKeys;
201
204
  private sequence;
202
205
  private eventCount;
203
206
  private byteCount;
204
207
  private subscriberCount;
205
208
  private usageClock;
206
209
  private closed;
210
+ private broadcast;
207
211
  constructor(options?: ProjectRoomEventBrokerOptions);
208
212
  /** Number of currently registered subscriptions across all rooms. */
209
213
  get activeSubscriberCount(): number;
210
214
  /** Exact UTF-8 byte count of all retained serialized scoped events. */
211
215
  get retainedBytes(): number;
212
- /** Atomically stores an immutable event before queueing detached listener callbacks. */
213
- publish(scope: ProjectRoomEventScope, draft: ProjectRoomBusinessEventDraft): ProjectRoomScopedBusinessEvent;
216
+ /**
217
+ * Installs (or clears) a hook invoked after a locally published event is stored
218
+ * and queued to local subscribers. Used to mirror events to other replicas via
219
+ * a shared transport. Best-effort: a throwing hook never affects `publish`.
220
+ */
221
+ setBroadcastHook(hook: ((scope: ProjectRoomEventScope, serializedEvent: string, sourceKey?: string) => void) | undefined): void;
222
+ /**
223
+ * Atomically stores an immutable event before queueing detached listener callbacks.
224
+ *
225
+ * @param sourceKey Optional caller-owned fact key; when present it is retained
226
+ * for bounded cross-replica duplicate suppression so a later foreign copy of the
227
+ * same fact (or a local publish) is dropped by {@link ingestForeignEvent}.
228
+ */
229
+ publish(scope: ProjectRoomEventScope, draft: ProjectRoomBusinessEventDraft, sourceKey?: string): ProjectRoomScopedBusinessEvent;
230
+ /**
231
+ * Delivers one event published by ANOTHER replica to this process's local
232
+ * subscribers for its room, without adding it to the local replay ring or
233
+ * consuming a local sequence (its id carries the origin replica's epoch).
234
+ *
235
+ * @param serializedEvent A serialized scoped business event produced by a peer.
236
+ * @param sourceKey Optional caller-owned fact key shared by every replica's copy
237
+ * of the same committed fact. When it was already seen (locally published or
238
+ * ingested from a peer) the event is dropped to avoid N× duplicate delivery.
239
+ * @returns True when it was queued to at least one local subscription.
240
+ * @remarks Source-key deduplication is process-local and bounded, so it caps
241
+ * duplicate fan-out within the retention window without replacing REST, which
242
+ * remains the authority. A malformed payload or a room with no local
243
+ * subscribers is ignored.
244
+ */
245
+ ingestForeignEvent(serializedEvent: string, sourceKey?: string): boolean;
214
246
  /** Atomically resolves replay and registers a distinct subscription for live events. */
215
247
  subscribe(scope: ProjectRoomEventScope, afterEventId: string | undefined, listener: Listener): ProjectRoomEventSubscription;
216
248
  /** Clears retained state, marks every subscription closed, and permanently closes the broker. */
@@ -232,6 +264,7 @@ declare class ProjectRoomEventBroker {
232
264
  private assertOpen;
233
265
  private createRoom;
234
266
  private touch;
267
+ private rememberSourceKey;
235
268
  }
236
269
 
237
270
  /**
@@ -307,11 +340,18 @@ interface ProjectTaskMachineScope {
307
340
  /**
308
341
  * Serializes Project task machine operations by tenant, Assistant, and Project.
309
342
  *
310
- * This mutex is process-local: callers must share one instance, and deployments
311
- * with multiple Gateway processes need an external lease for cross-process
312
- * exclusion. Acquisition is FIFO and intentionally non-reentrant; an operation
313
- * must not await another acquisition of its own machine key. When an operation
314
- * also needs the Project mutation mutex, it must acquire this machine mutex first.
343
+ * The local FIFO lock is process-local: callers must share one instance.
344
+ * Acquisition is FIFO and intentionally non-reentrant; an operation must not
345
+ * await another acquisition of its own machine key. When an operation also needs
346
+ * the Project mutation mutex, it must acquire this machine mutex first.
347
+ *
348
+ * A coordination lease store may be installed process-wide
349
+ * ({@link configureCoordinationLeaseStore}). When present, each critical section
350
+ * additionally holds a bounded distributed lease
351
+ * (`project-task-machine:<tenant>:<assistant>:<project>`) so operations on the
352
+ * same machine serialize across replicas, not just within this process. The local
353
+ * FIFO lock is always retained; the lease only adds cross-replica exclusion. When
354
+ * no store is configured (single process, tests), behavior is unchanged.
315
355
  */
316
356
  declare class ProjectTaskExecutionMutex {
317
357
  private readonly queues;
@@ -397,6 +437,11 @@ type ProjectTaskRunOutcome = {
397
437
  task: TaskItem;
398
438
  thread: Thread;
399
439
  messageId: string;
440
+ } | {
441
+ kind: "deferred";
442
+ task: TaskItem;
443
+ thread: Thread;
444
+ messageId: string;
400
445
  };
401
446
  /** Stable classification of the queue fact used to recover an in-progress Project Task. */
402
447
  type ProjectTaskRecoveryClassification = {
@@ -764,6 +809,313 @@ declare class AgentTaskConsumer {
764
809
  trigger_agent_task(taskRequest: AgentTaskRequest): void;
765
810
  }
766
811
 
812
+ /** Cross-replica liveness probe: resolves true when a peer still owns the task's run. */
813
+ type TaskLivenessProbe = (task: TaskItem) => Promise<boolean>;
814
+ /**
815
+ * Builds a cross-replica task-liveness probe from the registered `"lease"` store.
816
+ *
817
+ * @returns A {@link TaskLivenessProbe}, or `undefined` only when no `lease`
818
+ * store is registered (a test-only path); with the default in-memory lease
819
+ * store a probe is always returned (single-process safe because the in-memory
820
+ * store has no peer).
821
+ * @remarks
822
+ * A task is considered live when EITHER lease is currently held:
823
+ * - `task-run:<taskId>` — the immediate executor's run-lifetime lease, or
824
+ * - `thread-run:<tenantId>:<ownerId>:<thread_id>` — the queue processor
825
+ * executing the task's run thread (dispatched workers run through this).
826
+ *
827
+ * Fail-safe: any lease-store error resolves `true`, so an unknown liveness
828
+ * state never causes a live run to be converged as an orphan.
829
+ *
830
+ * The probe is advisory: there is a benign race between the probe read and
831
+ * `failTask` (a run could acquire its lease in that window), and no
832
+ * fencing/heartbeat-expiry detection is provided yet — consistent with the
833
+ * framework's current no-lease posture.
834
+ *
835
+ * @example
836
+ * ```ts
837
+ * const probe = createTaskRunLiveness();
838
+ * if (probe && await probe(task)) return; // a peer replica owns it
839
+ * ```
840
+ */
841
+ declare function createTaskRunLiveness(): TaskLivenessProbe | undefined;
842
+
843
+ /** Minimal caller-graph client surface used by recovery. */
844
+ interface RecoveryCallerClient {
845
+ getState(config: unknown): Promise<unknown>;
846
+ updateState(config: unknown, values: unknown, asNode?: string): Promise<unknown>;
847
+ invoke(input: unknown, config: unknown): Promise<unknown>;
848
+ }
849
+ interface TopCallerRecoveryDeps {
850
+ /**
851
+ * Resolve a caller's agent client + config from the resolved top caller.
852
+ *
853
+ * @param top The resolved top-level caller thread + assistant.
854
+ * @param tenantId The orphan task's tenant, used to scope the caller client.
855
+ */
856
+ callerFor(top: {
857
+ threadId: string;
858
+ assistantId?: string;
859
+ }, tenantId: string): Promise<{
860
+ client: RecoveryCallerClient;
861
+ config: unknown;
862
+ agent?: unknown;
863
+ } | undefined>;
864
+ /** Live-run registry (defaults to the shared Core singleton). */
865
+ registry?: {
866
+ isLive(taskId: string): boolean;
867
+ };
868
+ /**
869
+ * Optional cross-replica liveness probe consulted by Guard B, so a run held
870
+ * by a peer replica is not converged/backfilled. Mutually additive with the
871
+ * process-local `registry`.
872
+ */
873
+ isTaskLive?: TaskLivenessProbe;
874
+ /** Resume the continuation (tests can call client.invoke(null, config)). */
875
+ resume(client: RecoveryCallerClient, config: unknown, agent?: unknown): Promise<void>;
876
+ }
877
+ /** Dependencies for an on-demand single-task recovery ({@link recoverTask}). */
878
+ interface RecoverTaskDeps {
879
+ /** Lifecycle used to converge the orphan to `failed(no_executor)`. */
880
+ lifecycle: Pick<TaskLifecycleService, "failTask">;
881
+ /** Live-run registry (defaults to the shared Core singleton). */
882
+ registry?: TaskRunRegistry;
883
+ /**
884
+ * The tenant's other orphan candidates (normally every `in_progress` task),
885
+ * used to walk the caller chain up to the true top-level caller.
886
+ *
887
+ * @remarks
888
+ * Without this set {@link resolveTopCaller} can only see the target itself,
889
+ * so a nested middle run would be mistaken for a direct child of its
890
+ * immediate (frozen) caller. Defaults to `[converged]` when omitted.
891
+ */
892
+ orphans?: TaskItem[];
893
+ /**
894
+ * Optional cross-replica liveness probe consulted by Guard B, so a run held
895
+ * by a peer replica is not converged. Forwarded to
896
+ * {@link reconcileStaleTaskRuns} and to {@link recoverTopCaller}.
897
+ */
898
+ isTaskLive?: TaskLivenessProbe;
899
+ /** Resolve a caller's agent client + config from the resolved top caller. */
900
+ callerFor: TopCallerRecoveryDeps["callerFor"];
901
+ /** Resume the continuation (tests can call client.invoke(null, config)). */
902
+ resume: TopCallerRecoveryDeps["resume"];
903
+ }
904
+ /**
905
+ * Outcome of an on-demand single-task recovery attempt.
906
+ *
907
+ * @remarks
908
+ * - `recovered`: the task was converged and its top-level caller was resumed.
909
+ * - `skipped`: nothing was changed; `reason` names the guard (for example
910
+ * `not_in_progress`, `live`, `middle`, `project_task`, `no_top_caller`,
911
+ * `not_found`).
912
+ * - `converged_no_call`: the task was converged to `failed`, but the caller had
913
+ * no pending `task` call left to resolve, so no continuation was resumed.
914
+ */
915
+ type RecoverTaskResult = {
916
+ kind: "recovered";
917
+ } | {
918
+ kind: "skipped";
919
+ reason: string;
920
+ } | {
921
+ kind: "converged_no_call";
922
+ };
923
+ /**
924
+ * Backfills one converged direct-child task's top-level caller.
925
+ *
926
+ * @param task The converged orphan task (must carry `context.caller_thread_id`).
927
+ * @param top The resolved top-level caller.
928
+ * @param deps Caller resolver, live-run registry, and resume hook.
929
+ * @returns `"recovered"` or a `"skipped:<reason>"` string.
930
+ */
931
+ declare function recoverTopCaller(task: TaskItem, top: {
932
+ threadId: string;
933
+ assistantId?: string;
934
+ }, deps: TopCallerRecoveryDeps): Promise<string>;
935
+ /**
936
+ * Recovers a single orphan task on demand, reusing the collapse logic.
937
+ *
938
+ * @param task The candidate task (any status).
939
+ * @param deps Lifecycle, live-run registry, orphan candidate set (for the
940
+ * caller-chain walk), caller resolver, and resume hook.
941
+ * @returns A discriminated outcome: `recovered`, `skipped:<reason>`, or
942
+ * `converged_no_call` when the task was converged but the caller had no
943
+ * pending `task` call to resolve.
944
+ * @remarks
945
+ * Mirrors {@link runOrphanRecovery} for exactly one task: anything not
946
+ * `in_progress`, or still held by a live run, is skipped; the rest is converged
947
+ * to `failed` (`no_executor`, ADR-143) and its **top-level** caller is
948
+ * backfilled. `deps.orphans` must carry the tenant's other `in_progress`
949
+ * candidates so a nested middle run is correctly skipped (`middle`) instead of
950
+ * being mistaken for a direct child of its frozen immediate caller.
951
+ *
952
+ * This is an **offline/maintenance** entry point (the `task:recover` CLI): run
953
+ * it when the Gateway is stopped, or from within the Gateway process. Guard B
954
+ * reads the process-local {@link taskRunRegistry}; when the optional
955
+ * `isTaskLive` cross-replica probe is supplied (the gateway's lease-backed
956
+ * probe), it also detects a run held by a peer replica.
957
+ *
958
+ * The convergence actor is `system:on-demand-recovery` (distinct from the
959
+ * startup sweep's `system:orphan-recovery`), so on-demand convergences are
960
+ * auditable in the task work-item trail. No HTTP route is added and recovery
961
+ * semantics are unchanged.
962
+ */
963
+ declare function recoverTask(task: TaskItem, deps: RecoverTaskDeps): Promise<RecoverTaskResult>;
964
+ /** Walks caller links up to the thread that is not any orphan's run thread. */
965
+ declare function resolveTopCaller(task: TaskItem, orphans: TaskItem[]): {
966
+ threadId: string;
967
+ assistantId?: string;
968
+ } | undefined;
969
+ /**
970
+ * Runs the whole collapse recovery once: reconcile orphan tasks, then backfill
971
+ * the top-level caller of every direct-child orphan, grouped per top caller.
972
+ * Best-effort; a failing group is contained and never aborts the others.
973
+ *
974
+ * @param deps Reconcile source, caller resolution, resume, and logger.
975
+ * @returns The recovered and skipped `taskId:reason` entries.
976
+ */
977
+ declare function runOrphanRecovery(deps: {
978
+ reconcile: () => Promise<{
979
+ convergedTasks: TaskItem[];
980
+ }>;
981
+ callerFor: TopCallerRecoveryDeps["callerFor"];
982
+ resume: TopCallerRecoveryDeps["resume"];
983
+ isTaskLive?: TaskLivenessProbe;
984
+ log: {
985
+ info(msg: string, ctx: Record<string, unknown>): void;
986
+ warn(msg: string, ctx: Record<string, unknown>): void;
987
+ };
988
+ }): Promise<{
989
+ recovered: string[];
990
+ skipped: string[];
991
+ }>;
992
+
993
+ /**
994
+ * Shared orphan-recovery runtime: the startup sweep and the on-demand
995
+ * single-task entry point share one dependency shape and one lifecycle.
996
+ *
997
+ * @remarks
998
+ * `recoverAll()` is the behavior-preserving move of the gateway startup block
999
+ * (reconcile every tenant's orphaned ordinary tasks, then backfill the
1000
+ * top-level caller). `recoverTask()` is the A-lite on-demand entry: no HTTP
1001
+ * route, just a programmatic call used by the `task:recover` CLI/script.
1002
+ */
1003
+
1004
+ /** Dependencies shared by the startup sweep and the single-task entry. */
1005
+ interface OrphanRecoveryRuntimeDeps {
1006
+ /** Task store used to enumerate tenants' orphans and read one task by id. */
1007
+ taskStore: TaskStore;
1008
+ /** Work-item store used to build the lifecycle when `lifecycle` is omitted. */
1009
+ workItemStore: TaskWorkItemStore;
1010
+ /** Tenant store supplying `getAllTenants()` for the startup sweep. */
1011
+ tenantStore: Pick<TenantStore, "getAllTenants">;
1012
+ /** Lifecycle used to converge orphans (built from the stores when omitted). */
1013
+ lifecycle?: Pick<TaskLifecycleService, "failTask">;
1014
+ /** Resolve a caller's agent client + config from the resolved top caller. */
1015
+ callerFor: TopCallerRecoveryDeps["callerFor"];
1016
+ /** Resume the continuation (tests can call client.invoke(null, config)). */
1017
+ resume: TopCallerRecoveryDeps["resume"];
1018
+ /**
1019
+ * Cross-replica liveness probe; defaults to the shared cross-replica probe
1020
+ * ({@link createTaskRunLiveness()}); inherit it unless a test injects one.
1021
+ */
1022
+ isTaskLive?: TaskLivenessProbe;
1023
+ /** Structured logger. */
1024
+ log: {
1025
+ info(msg: string, ctx: Record<string, unknown>): void;
1026
+ warn(msg: string, ctx: Record<string, unknown>): void;
1027
+ };
1028
+ }
1029
+ /** Entry points returned by {@link createOrphanRecoveryRuntime}. */
1030
+ interface OrphanRecoveryRuntime {
1031
+ /** Startup/interval sweep over every tenant's orphaned ordinary tasks. */
1032
+ recoverAll(): Promise<{
1033
+ recovered: string[];
1034
+ skipped: string[];
1035
+ }>;
1036
+ /**
1037
+ * On-demand recovery of exactly one task within one tenant.
1038
+ *
1039
+ * @remarks Offline/maintenance: run when the Gateway is stopped, or from the
1040
+ * Gateway process. The `live` guard reads the process-local task registry and,
1041
+ * when a lease store is registered, also consults the cross-replica liveness
1042
+ * probe so a run held by another process is not converged. Governed Project
1043
+ * tasks are refused (`skipped:project_task`) so this never bypasses their
1044
+ * managed runtime.
1045
+ */
1046
+ recoverTask(input: {
1047
+ tenantId: string;
1048
+ taskId: string;
1049
+ }): Promise<RecoverTaskResult>;
1050
+ }
1051
+ /**
1052
+ * Builds the shared orphan-recovery runtime.
1053
+ *
1054
+ * @param deps Task/work-item/tenant stores, optional lifecycle, caller
1055
+ * resolver, resume hook, and logger.
1056
+ * @returns `recoverAll()` (startup sweep) and `recoverTask()` (on-demand).
1057
+ * @remarks
1058
+ * Governed Project tasks are excluded from `recoverAll()` (they have their own
1059
+ * observer-based reconciliation) and from `recoverTask()`
1060
+ * (`skipped:project_task`), so the on-demand entry can never bypass the managed
1061
+ * Project runtime. `recoverTask()` delegates to {@link recoverSingleTask} with
1062
+ * the shared lifecycle, the process-local live-run registry, and the
1063
+ * cross-replica liveness probe (built from the shared `"lease"` store unless
1064
+ * `isTaskLive` is supplied), so it does not converge a task a live run still
1065
+ * holds — in this process or a peer replica. The liveness probe is advisory: a
1066
+ * benign race exists between the probe read and `failTask` (no
1067
+ * fencing/heartbeat support yet).
1068
+ *
1069
+ * @example
1070
+ * ```ts
1071
+ * const runtime = createOrphanRecoveryRuntime({ taskStore, workItemStore, tenantStore, callerFor, resume, log });
1072
+ * await runtime.recoverAll();
1073
+ * const outcome = await runtime.recoverTask({ tenantId, taskId });
1074
+ * ```
1075
+ */
1076
+ declare function createOrphanRecoveryRuntime(deps: OrphanRecoveryRuntimeDeps): OrphanRecoveryRuntime;
1077
+
1078
+ /**
1079
+ * Builds the caller-resolution and resume hooks used by orphan recovery.
1080
+ *
1081
+ * @returns `callerFor` (resolves a top caller's agent client + config, omitting
1082
+ * the in-memory `agent` when it is absent) and `resume` (prefers
1083
+ * `agent.resumeTask()`, otherwise a raw checkpoint continuation via
1084
+ * `client.invoke(null, config)`).
1085
+ * @remarks
1086
+ * `callerFor` uses `getAgentClientAsync` (falls back to `getAgentClient`) so a
1087
+ * DB-backed assistant that is not resident in memory can still be loaded.
1088
+ */
1089
+ declare function createRecoveryCallerDeps(): Pick<TopCallerRecoveryDeps, "callerFor" | "resume">;
1090
+
1091
+ /** Outbox topic carrying cross-replica Project Room realtime events. */
1092
+ declare const ROOM_EVENT_TOPIC = "room-event";
1093
+ /**
1094
+ * Mirrors one locally published Project Room event to the shared outbox.
1095
+ *
1096
+ * @param outbox Shared append-only outbox log.
1097
+ * @param owner Origin replica id, so its own poller skips the row.
1098
+ * @param scope Room scope the event was published for.
1099
+ * @param serialized Canonical serialized scoped event.
1100
+ * @param sourceKey Optional caller-owned fact key carried to peer replicas for
1101
+ * bounded duplicate suppression at their broker.
1102
+ * @returns A promise that settles once the append attempt completes.
1103
+ * @remarks Best-effort: an append failure is swallowed because REST remains the
1104
+ * authority and clients refetch on reconnect.
1105
+ */
1106
+ declare function publishRoomEvent(outbox: OutboxStore, owner: string, scope: ProjectRoomEventScope, serialized: string, sourceKey?: string): Promise<void>;
1107
+ /**
1108
+ * Delivers one foreign outbox payload to the local broker's subscribers.
1109
+ *
1110
+ * @param payload Raw outbox payload; expected `{ serialized: string, sourceKey?: string }`.
1111
+ * @param broker The process-local Project Room event broker.
1112
+ * @returns True when the broker queued the event to at least one local subscription.
1113
+ * @remarks Malformed payloads are ignored. The broker drops events whose
1114
+ * source key it already saw; the poller additionally skips rows authored by this
1115
+ * replica.
1116
+ */
1117
+ declare function ingestRoomEvent(payload: unknown, broker: ProjectRoomEventBroker): boolean;
1118
+
767
1119
  /**
768
1120
  * Logger configuration for gateway
769
1121
  */
@@ -794,4 +1146,4 @@ declare const LatticeGateway: {
794
1146
  AgentTaskConsumer: typeof AgentTaskConsumer;
795
1147
  };
796
1148
 
797
- export { type GatewayLoggerConfig, LatticeGateway, type LatticeGatewayConfig };
1149
+ export { type GatewayLoggerConfig, LatticeGateway, type LatticeGatewayConfig, type OrphanRecoveryRuntime, type OrphanRecoveryRuntimeDeps, ProjectRoomEventBroker, ROOM_EVENT_TOPIC, type RecoverTaskDeps, type RecoverTaskResult, type TaskLivenessProbe, createOrphanRecoveryRuntime, createRecoveryCallerDeps, createTaskRunLiveness, ingestRoomEvent, publishRoomEvent, recoverTask, recoverTopCaller, resolveTopCaller, runOrphanRecovery };