@intx/hub-sessions 0.3.0 → 0.4.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 (67) hide show
  1. package/dist/agent-repo.d.ts +14 -2
  2. package/dist/agent-repo.js +17 -4
  3. package/dist/agent-state-kind.js +14 -63
  4. package/dist/asset-service.js +14 -10
  5. package/dist/credential-push.d.ts +48 -4
  6. package/dist/credential-push.js +138 -6
  7. package/dist/event-collector-registry.d.ts +2 -1
  8. package/dist/event-collector-registry.js +38 -9
  9. package/dist/event-collector.d.ts +11 -1
  10. package/dist/event-collector.js +36 -3
  11. package/dist/hub-session-lookups.d.ts +1 -1
  12. package/dist/hub-session-lookups.js +68 -72
  13. package/dist/hub-session-orchestrator.d.ts +2 -3
  14. package/dist/hub-session-orchestrator.js +13 -12
  15. package/dist/index.d.ts +7 -6
  16. package/dist/index.js +7 -6
  17. package/dist/reconciliation-scheduler.d.ts +14 -0
  18. package/dist/reconciliation-scheduler.js +55 -0
  19. package/dist/repo-store/index.d.ts +1 -0
  20. package/dist/repo-store/index.js +1 -0
  21. package/dist/repo-store/user-principal-gate.d.ts +26 -0
  22. package/dist/repo-store/user-principal-gate.js +78 -0
  23. package/dist/session-service.d.ts +66 -121
  24. package/dist/session-service.js +444 -411
  25. package/dist/sidecar-allocation/capability-policy.d.ts +27 -0
  26. package/dist/sidecar-allocation/capability-policy.js +124 -0
  27. package/dist/sidecar-allocation/contracts.d.ts +29 -6
  28. package/dist/sidecar-allocation/contracts.js +7 -2
  29. package/dist/sidecar-allocation/index.d.ts +4 -3
  30. package/dist/sidecar-allocation/index.js +3 -2
  31. package/dist/sidecar-allocation/operation.d.ts +10 -0
  32. package/dist/sidecar-allocation/operation.js +54 -0
  33. package/dist/sidecar-allocation/plugin-registry.d.ts +16 -3
  34. package/dist/sidecar-allocation/plugin-registry.js +36 -12
  35. package/dist/sidecar-allocation/reconciler.d.ts +16 -4
  36. package/dist/sidecar-allocation/reconciler.js +486 -92
  37. package/dist/skill-kind.js +8 -62
  38. package/dist/substrate.d.ts +1 -1
  39. package/dist/substrate.js +1 -1
  40. package/dist/workflow-allocation-service.d.ts +21 -15
  41. package/dist/workflow-allocation-service.js +440 -125
  42. package/dist/workflow-dispatch-service.d.ts +4 -2
  43. package/dist/workflow-dispatch-service.js +89 -26
  44. package/dist/workflow-kind.d.ts +12 -0
  45. package/dist/workflow-kind.js +17 -60
  46. package/dist/workflow-probe-gate.d.ts +99 -27
  47. package/dist/workflow-probe-gate.js +196 -21
  48. package/dist/workflow-run-kind.d.ts +112 -19
  49. package/dist/workflow-run-kind.js +626 -210
  50. package/dist/workflow-run-restore.d.ts +1 -0
  51. package/dist/workflow-run-restore.js +5 -1
  52. package/dist/workflow-source-pins.d.ts +8 -0
  53. package/dist/workflow-source-pins.js +14 -0
  54. package/dist/ws/index.d.ts +1 -1
  55. package/dist/ws/index.js +1 -1
  56. package/dist/ws/pending-tracker.d.ts +93 -0
  57. package/dist/ws/pending-tracker.js +132 -0
  58. package/dist/ws/sidecar-events.d.ts +43 -29
  59. package/dist/ws/sidecar-events.js +0 -2
  60. package/dist/ws/sidecar-handler.d.ts +122 -85
  61. package/dist/ws/sidecar-handler.js +925 -878
  62. package/dist/ws/sidecar-handler.test-helpers.d.ts +38 -0
  63. package/dist/ws/sidecar-handler.test-helpers.js +95 -0
  64. package/dist/ws/sidecar-token-authenticator.js +37 -23
  65. package/package.json +13 -13
  66. package/dist/sidecar-allocation/placement-policy.d.ts +0 -11
  67. package/dist/sidecar-allocation/placement-policy.js +0 -21
@@ -5,6 +5,27 @@ import type { SidecarCredentialIdentity } from "../sidecar-allocation/contracts.
5
5
  import type { ToolPackageManifest } from "@intx/types/tool-packages";
6
6
  import type { WorkflowDefinitionSource } from "@intx/types/workflow-sources";
7
7
  import { type SidecarEventEmitter, type SidecarLookups } from "./sidecar-events.js";
8
+ import { type WsHandle } from "./pending-tracker.js";
9
+ /**
10
+ * A deploy-frame send failure, tagged with whether the `agent.deploy` frame
11
+ * reached the wire. `frameSent: false` means the send was refused before
12
+ * `conn.send` (a guard failed, or the send threw synchronously) -- the deploy
13
+ * provably never started, so a caller may safely roll back anything it staged.
14
+ * `frameSent: true` means the frame was sent and the failure came afterward (ack
15
+ * timeout, sidecar disconnect), so the sidecar may hold a live agent.
16
+ */
17
+ export interface DeployFrameFailure extends Error {
18
+ readonly frameSent: boolean;
19
+ }
20
+ export declare function isDeployFrameFailure(err: unknown): err is DeployFrameFailure;
21
+ /**
22
+ * Identity validation failed or remained pending at the connection deadline.
23
+ * Readiness is unknown: the worker may be healthy behind the lookup, so
24
+ * callers must retry rather than treat this as a missed connection deadline.
25
+ */
26
+ export declare class SidecarIdentityValidationError extends Error {
27
+ constructor(allocationId: string, generation: number, cause?: unknown);
28
+ }
8
29
  export type SidecarConnection = {
9
30
  sidecarId: string;
10
31
  identity: SidecarAuthIdentity;
@@ -57,11 +78,31 @@ export type WorkflowProbeResult = {
57
78
  grantWalkSnapshot: GrantWalkSnapshot;
58
79
  wireHash: string;
59
80
  };
81
+ /**
82
+ * The result of a run-address sender's deploy, reported to the router once the
83
+ * deploy's key write is durable. `recorded` carries the sender's now-persisted
84
+ * public key and wakes the sender's parked pre-ack mail for redelivery;
85
+ * `failed` carries a failure reason and drains that mail to
86
+ * `mail.outbound.undelivered`. Modeled as a discriminated result so a settle is
87
+ * unambiguous about which side of the deploy it reports.
88
+ */
89
+ export type SenderDeploySettledOutcome = {
90
+ recorded: string;
91
+ } | {
92
+ failed: string;
93
+ };
94
+ export type AllocatedSenderDeployAttempt = AllocatedSidecarTarget & {
95
+ readonly leaseId: string;
96
+ };
60
97
  export type SidecarRouter = {
61
98
  handleOpen(ws: WsHandle): void;
62
99
  handleMessage(ws: WsHandle, data: string): void;
63
100
  handleClose(ws: WsHandle): void;
64
- routeMail(agentAddress: string, rawMessage: string, messageId?: string): boolean;
101
+ routeMail(agentAddress: string, rawMessage: string, authenticatedSender: string, messageId?: string, runGrants?: {
102
+ runId: string;
103
+ stepGrants: RunGrantsFrame["stepGrants"];
104
+ senderIdentities?: RunGrantsFrame["senderIdentities"];
105
+ }): boolean;
65
106
  /**
66
107
  * Deliver a run's authorization grants to the sidecar hosting the named
67
108
  * deployment-level mail address, ahead of the trigger mail that starts the
@@ -70,14 +111,40 @@ export type SidecarRouter = {
70
111
  * queue when the deployment dropped in the window before its first
71
112
  * reconnect (while its address is still on `agentAddresses`) -- so grants
72
113
  * are queued for a disconnected deployment exactly when the trigger mail is,
73
- * and ride the same reconnect flush. After a challenged reconnect the
114
+ * and ride the same reconnect flush. After an authenticated reconnect the
74
115
  * address moves to `workflowAddresses`, which carries no queue (that
75
116
  * generation's in-flight state is reconstructed sidecar-locally); a
76
117
  * `run.grants` then has no queue to ride and this returns `false`. Returns
77
118
  * `false` whenever the address is unroutable; the caller keeps any stable-run
78
119
  * grant reservation so a later first-delivery attempt reuses it.
120
+ *
121
+ * `senderIdentities` co-delivers the run's authorized senders' resolved keys
122
+ * on the same barrier as the grant, so a recipient that caches from this
123
+ * frame binds each sender address to the hub-vouched key. The caller passes
124
+ * `undefined` when there is no sender to co-deliver (a standing-grant refresh)
125
+ * or the sender has no resolvable key; a null key is never carried.
79
126
  */
80
- sendRunGrants(agentAddress: string, runId: string, stepGrants: RunGrantsFrame["stepGrants"]): boolean;
127
+ sendRunGrants(agentAddress: string, runId: string, stepGrants: RunGrantsFrame["stepGrants"], senderIdentities: RunGrantsFrame["senderIdentities"]): boolean;
128
+ /**
129
+ * Report that a run-address sender's deploy has settled, driving any mail the
130
+ * sender parked while its public key was not yet recorded. A `recorded`
131
+ * outcome wakes the parked mail and re-drives its delivery now that the key
132
+ * co-delivers; a `failed` outcome drains it to `mail.outbound.undelivered`.
133
+ * Allocated callbacks name their exact attempt; recovery may settle the
134
+ * previous attempt by generation after claiming its reconciliation lease.
135
+ * An address-only settlement belongs to a non-allocated deployment and cannot
136
+ * settle an allocated attempt. Stale or repeated settlements are no-ops.
137
+ */
138
+ noteSenderDeploySettled(sender: string | AllocatedSidecarTarget | AllocatedSenderDeployAttempt, outcome: SenderDeploySettledOutcome): void;
139
+ /**
140
+ * Mark a run-address sender's ALLOCATED deploy as mid-flight, before the deploy
141
+ * emit and its anchor-key update. An allocated run records its key later than
142
+ * the deploy ack clears `pendingDeploys`, so this marker covers the allocated
143
+ * pre-ack window that `pendingDeploys` alone under-covers. `noteSenderDeploySettled`
144
+ * clears it only when the durable outcome is known. Cancellation leaves it
145
+ * pending for recovery, so a lost publication response cannot discard mail.
146
+ */
147
+ noteSenderDeployStarted(address: string, attempt: AllocatedSenderDeployAttempt): void;
81
148
  /**
82
149
  * Returns the current connector-thread state for the named agent, or
83
150
  * `null` if the agent has no active connector thread (or if the
@@ -88,65 +155,9 @@ export type SidecarRouter = {
88
155
  * default the calling path uses.
89
156
  */
90
157
  getConnectorState(agentAddress: string): ConnectorThreadState | null;
91
- /**
92
- * Send an `agent.deploy` frame to the sidecar. When `workflow` is
93
- * supplied, the frame carries the multi-step deploy projection
94
- * (workflow definition plus per-step source pins); the sidecar's
95
- * deploy router routes it to the workflow deploy path. The sole
96
- * caller supplies `workflow` on every deploy; per-step provisioning
97
- * uses `sendProvisionStep`.
98
- *
99
- * The returned promise resolves with the supervisor's principal
100
- * public key (hex-encoded Ed25519) carried on `agent.deploy.ack`.
101
- * The legacy callers that ignore the return value continue to work
102
- * unchanged.
103
- */
104
- sendAgentDeploy(agentAddress: string, config: HarnessConfig, workflow?: AgentDeployFrame["workflow"]): Promise<{
105
- publicKey: string;
106
- }>;
107
- /**
108
- * Ask a connected sidecar to probe a code-sourced workflow WITHOUT
109
- * deploying it, and resolve with the sidecar's inert answer (needs-surface
110
- * projection + grant set + content hash). Selects any connected sidecar via
111
- * `findSidecarForNewAgent` -- the probe runs in the sidecar's pre-deploy
112
- * state, so it needs no deployed agent and enters no address map -- and
113
- * correlates the round-trip purely by a minted `requestId`. Rejects if no
114
- * sidecar is connected, if the probe times out (`probeTimeoutMs`), if the
115
- * sidecar answers `workflow.probe.error`, or if the sidecar disconnects with
116
- * the probe in flight.
117
- *
118
- * Optional so existing `SidecarRouter` consumers -- and their test doubles
119
- * -- that never probe need not implement it, mirroring `DeployRouter`'s
120
- * optional `undeploy`. The concrete `createSidecarRouter` always provides
121
- * it.
122
- */
123
- sendProbe?(args: SendProbeArgs): Promise<WorkflowProbeResult>;
124
158
  sendAgentUndeploy(agentAddress: string, reason: string): Promise<void>;
125
159
  sendSourcesUpdate(agentAddress: string, sources: InferenceSource[], defaultSource: string): Promise<void>;
126
- sendCredentialsUpdate(agentAddress: string, delivery: CredentialDelivery): Promise<void>;
127
- sendPack(agentAddress: string, pack: Uint8Array, ref: string, commitSha: string, options?: SendPackOptions): Promise<void>;
128
- /**
129
- * Bind a per-step workflow-substrate address to a sidecar for the staging
130
- * window of a multi-step deploy, so `sendPack` can route the step's deploy
131
- * and asset packs before the deployment-level frame spawns the child. The
132
- * address enters the keyless `workflowAddresses` routing set; call
133
- * `unbindStepRoute` once the step's packs land. Throws if no sidecar is
134
- * available.
135
- */
136
- bindStepRoute(stepAddress: string): void;
137
- /**
138
- * Remove a per-step route bound by `bindStepRoute`. Idempotent: an unbound
139
- * address is a no-op.
140
- */
141
- unbindStepRoute(stepAddress: string): void;
142
- /**
143
- * Provision one step of a multi-step deploy on the sidecar WITHOUT
144
- * spawning: the sidecar initializes the step's agent-state repo and
145
- * records the hub key so the follow-up deploy pack applies and verifies.
146
- * The step address must already be bound via `bindStepRoute`. Resolves
147
- * once the sidecar acks, so the caller can then deliver the deploy pack.
148
- */
149
- sendProvisionStep(agentAddress: string, config: HarnessConfig): Promise<void>;
160
+ sendCredentialsUpdate(agentAddress: string, delivery: CredentialDelivery, revoke?: string[]): Promise<void>;
150
161
  sendSyncRequest(agentAddress: string): void;
151
162
  /**
152
163
  * Deliver a workflow-run signal to the sidecar that hosts the named
@@ -212,13 +223,31 @@ export type AllocatedSidecarTarget = {
212
223
  export type SidecarAllocationRouter = {
213
224
  /** Advance the in-memory trust boundary before provisioning a generation. */
214
225
  fenceAllocation(allocationId: string, generation: number): void;
215
- /** Resolve once the exact authenticated allocation generation is connected. */
216
- waitForAllocatedSidecar(target: AllocatedSidecarTarget, timeoutMs: number): Promise<void>;
217
- /** Check exact allocated readiness without parking a reconciliation worker. */
226
+ /**
227
+ * Remove an exact generation's fence after its durable owner becomes
228
+ * terminal. Durable identity validation rejects later stale reconnects.
229
+ */
230
+ retireAllocation(target: AllocatedSidecarTarget): void;
231
+ /**
232
+ * Resolve once the exact authenticated allocation generation is connected.
233
+ * Throws `SidecarIdentityValidationError` when readiness cannot be
234
+ * determined; only confirmed absence surfaces as a connection timeout.
235
+ * `onValidation` observes notification lookups that may outlive this wait.
236
+ */
237
+ waitForAllocatedSidecar(target: AllocatedSidecarTarget, timeoutMs: number, onValidation?: (validation: Promise<boolean>) => void): Promise<void>;
238
+ /**
239
+ * Check exact allocated readiness without parking a reconciliation worker.
240
+ * Throws `SidecarIdentityValidationError` when identity validation fails;
241
+ * `false` means the worker is confirmed absent or stale.
242
+ */
218
243
  isAllocatedSidecarReady(target: AllocatedSidecarTarget): Promise<boolean>;
219
- /** Check whether the exact generation already hosts its workflow supervisor. */
244
+ /** Check for an active supervisor, throwing when identity validation fails. */
220
245
  isAllocatedWorkflowActive(target: AllocatedSidecarTarget): Promise<boolean>;
221
- sendAgentDeployToAllocation(target: AllocatedSidecarTarget, agentAddress: string, config: HarnessConfig, workflow?: AgentDeployFrame["workflow"]): Promise<{
246
+ /** Probe a workflow on the exact provisioned allocation generation. */
247
+ sendProbeToAllocation(target: AllocatedSidecarTarget, args: SendProbeArgs): Promise<WorkflowProbeResult>;
248
+ /** Close an exact provisioned connection before changing its durable owner. */
249
+ disconnectAllocation(target: AllocatedSidecarTarget): void;
250
+ sendAgentDeployToAllocation(target: AllocatedSidecarTarget, agentAddress: string, config: HarnessConfig, workflow?: AgentDeployFrame["workflow"], signal?: AbortSignal, beforeSend?: () => Promise<void>): Promise<{
222
251
  publicKey: string;
223
252
  }>;
224
253
  sendPackToAllocation(target: AllocatedSidecarTarget, agentAddress: string, pack: Uint8Array, ref: string, commitSha: string, options?: SendPackOptions): Promise<void>;
@@ -226,7 +255,7 @@ export type SidecarAllocationRouter = {
226
255
  * Restore one Hub-authoritative workflow-run ref onto the exact allocation
227
256
  * generation before its deployment address is routed or supervisor spawned.
228
257
  */
229
- sendWorkflowRunPackToAllocation(target: AllocatedSidecarTarget, agentAddress: string, pack: Uint8Array, ref: string, commitSha: string): Promise<void>;
258
+ sendWorkflowRunPackToAllocation(target: AllocatedSidecarTarget, agentAddress: string, pack: Uint8Array, ref: string, commitSha: string, signal?: AbortSignal): Promise<void>;
230
259
  bindAllocatedStepRoute(target: AllocatedSidecarTarget, stepAddress: string): Promise<void>;
231
260
  unbindAllocatedStepRoute(target: AllocatedSidecarTarget, stepAddress: string): void;
232
261
  sendProvisionStepToAllocation(target: AllocatedSidecarTarget, agentAddress: string, config: HarnessConfig): Promise<void>;
@@ -237,15 +266,15 @@ export type SidecarAllocationRouter = {
237
266
  * durable-inbox acknowledgement is surfaced separately through
238
267
  * `mail.inbound.acknowledged`.
239
268
  */
240
- sendWorkflowRunDispatchToAllocation(target: AllocatedSidecarTarget, agentAddress: string, runId: string, stepGrants: RunGrantsFrame["stepGrants"], rawMessage: string, messageId: string): Promise<void>;
241
- /** Deliver an idempotent signal to the exact exclusive generation. */
269
+ sendWorkflowRunDispatchToAllocation(target: AllocatedSidecarTarget, agentAddress: string, runId: string, stepGrants: RunGrantsFrame["stepGrants"], rawMessage: string, authenticatedSender: string, messageId: string, signal?: AbortSignal): Promise<void>;
270
+ /** Deliver an idempotent signal to the exact provisioned generation. */
242
271
  sendSignalDeliverToAllocation(target: AllocatedSidecarTarget, opts: {
243
272
  agentAddress: string;
244
273
  runId: string;
245
274
  signalName: string;
246
275
  signalId: string;
247
276
  payload: unknown;
248
- }): Promise<void>;
277
+ }, signal?: AbortSignal): Promise<void>;
249
278
  };
250
279
  /**
251
280
  * Resolves the credentials a sidecar presents on the handshake to a
@@ -268,8 +297,7 @@ export type SidecarRouterConfig = {
268
297
  * unverified frame claim. Return null to reject the handshake. */
269
298
  authenticateSidecar: SidecarAuthenticator;
270
299
  /** Revalidate durable identity at registration and routing boundaries. */
271
- validateSidecarIdentity?: (identity: SidecarAuthIdentity, use: "registration" | "readiness" | "routing") => Promise<boolean>;
272
- challengeTimeoutMs?: number;
300
+ validateSidecarIdentity: (identity: SidecarAuthIdentity, use: "registration" | "readiness" | "routing") => Promise<boolean>;
273
301
  /** Timeout for a `sendProbe` round-trip. A probe materializes a workflow's
274
302
  * dependency closure and evaluates it on the sidecar, so it can run longer
275
303
  * than a routine `sendRequest`; it gets its own timeout rather than sharing
@@ -281,25 +309,34 @@ export type SidecarRouterConfig = {
281
309
  /** Interval between redelivery attempts of a connected-window `mail.inbound`
282
310
  * the sidecar has not yet acknowledged with `mail.inbound.ack`. */
283
311
  mailAckRetryIntervalMs?: number;
312
+ /**
313
+ * Arms the mail-redelivery retry and the connection-liveness timers, and
314
+ * returns each one's canceller. Defaults to the global timer, which is what
315
+ * production wants.
316
+ *
317
+ * The intervals beside it say how long until something should happen; this
318
+ * says what makes it happen. With only the intervals injectable, a test had
319
+ * to shorten one and then sleep past it, which turns an assertion about
320
+ * WHETHER something happened into a bet on how much the machine got through
321
+ * -- and, for the liveness deadline, on a pause landing inside a window
322
+ * rather than past it.
323
+ *
324
+ * REQUIRED of the returned canceller: calling it more than once must be
325
+ * harmless. A pending-mail entry outlives a disconnect, so the disconnect
326
+ * path cancels its retry and a later reconnect can cancel the same one
327
+ * again. `clearTimeout` on an already-cleared timer is a no-op, which is
328
+ * what makes the default satisfy this; a substitute must arrange the same,
329
+ * typically by flipping a flag.
330
+ */
331
+ scheduleTimeout?: (handler: () => void, ms: number) => () => void;
284
332
  /** Maximum redelivery attempts before the hub stops retrying an un-acked
285
333
  * connected-window `mail.inbound`. Bounds the retry so a sidecar that never
286
334
  * acks does not accumulate an unbounded timer per delivery. */
287
335
  mailAckMaxRetries?: number;
288
- /** Query handlers the wire layer issues during frame processing.
289
- * Each lookup is one-handler-returns-a-value; for multi-subscriber
290
- * notifications use `router.events.on(...)` instead.
291
- *
292
- * `lookupDeployRef` and the `deploy.ref.stale` event are paired by
293
- * convention: the wire layer only issues the staleness comparison
294
- * when the lookup is set, and only emits the event on a confirmed
295
- * mismatch. The host is responsible for subscribing a listener
296
- * whenever the lookup is provided; the router does not enforce
297
- * the pairing. */
336
+ /** Query handlers the wire layer issues during frame processing. */
298
337
  lookups?: SidecarLookups;
299
338
  };
300
- export type WsHandle = {
301
- send(data: string): void;
302
- close(): void;
303
- };
339
+ export type { WsHandle };
304
340
  export declare const DEFAULT_PROBE_TIMEOUT_MS = 60000;
341
+ export declare const MAX_RESYNC_SENDER_ADDRESSES = 2048;
305
342
  export declare function createSidecarRouter(config: SidecarRouterConfig): SidecarRouter & SidecarAllocationRouter;