@intx/hub-sessions 0.1.2 → 0.3.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 (113) hide show
  1. package/LICENSE +176 -0
  2. package/README.md +82 -1
  3. package/dist/agent-repo.d.ts +93 -0
  4. package/dist/agent-repo.js +109 -0
  5. package/dist/agent-state-kind.d.ts +12 -0
  6. package/dist/agent-state-kind.js +189 -0
  7. package/dist/asset-service.d.ts +104 -0
  8. package/dist/asset-service.js +267 -0
  9. package/dist/committed-source-tree.d.ts +10 -0
  10. package/dist/committed-source-tree.js +35 -0
  11. package/dist/credential-push.d.ts +33 -0
  12. package/dist/credential-push.js +109 -0
  13. package/dist/event-collector-registry.d.ts +20 -0
  14. package/dist/event-collector-registry.js +115 -0
  15. package/dist/event-collector.d.ts +39 -0
  16. package/dist/event-collector.js +365 -0
  17. package/dist/hub-session-lookups.d.ts +135 -0
  18. package/dist/hub-session-lookups.js +663 -0
  19. package/dist/hub-session-orchestrator.d.ts +25 -0
  20. package/dist/hub-session-orchestrator.js +87 -0
  21. package/dist/index.d.ts +27 -0
  22. package/dist/index.js +24 -0
  23. package/dist/package-registry-kind.d.ts +70 -0
  24. package/dist/package-registry-kind.js +260 -0
  25. package/dist/repo-store/index.d.ts +4 -0
  26. package/dist/repo-store/index.js +3 -0
  27. package/dist/repo-store/store.d.ts +41 -0
  28. package/dist/repo-store/store.js +1829 -0
  29. package/dist/repo-store/subscribe-kind.d.ts +56 -0
  30. package/dist/repo-store/subscribe-kind.js +144 -0
  31. package/dist/repo-store/types.d.ts +571 -0
  32. package/dist/repo-store/types.js +42 -0
  33. package/dist/session-service.d.ts +416 -0
  34. package/dist/session-service.js +1191 -0
  35. package/dist/sidecar-allocation/contracts.d.ts +78 -0
  36. package/dist/sidecar-allocation/contracts.js +21 -0
  37. package/dist/sidecar-allocation/index.d.ts +4 -0
  38. package/dist/sidecar-allocation/index.js +3 -0
  39. package/dist/sidecar-allocation/placement-policy.d.ts +11 -0
  40. package/dist/sidecar-allocation/placement-policy.js +21 -0
  41. package/dist/sidecar-allocation/plugin-registry.d.ts +11 -0
  42. package/dist/sidecar-allocation/plugin-registry.js +37 -0
  43. package/dist/sidecar-allocation/reconciler.d.ts +42 -0
  44. package/dist/sidecar-allocation/reconciler.js +431 -0
  45. package/dist/skill-kind.d.ts +41 -0
  46. package/dist/skill-kind.js +292 -0
  47. package/dist/substrate.d.ts +8 -0
  48. package/dist/substrate.js +21 -0
  49. package/dist/workflow-allocation-service.d.ts +58 -0
  50. package/dist/workflow-allocation-service.js +239 -0
  51. package/dist/workflow-closure-resolution.d.ts +106 -0
  52. package/dist/workflow-closure-resolution.js +123 -0
  53. package/dist/workflow-definition-ensure.d.ts +24 -0
  54. package/dist/workflow-definition-ensure.js +75 -0
  55. package/dist/workflow-dispatch-service.d.ts +40 -0
  56. package/dist/workflow-dispatch-service.js +146 -0
  57. package/dist/workflow-dispatch-settlement.d.ts +29 -0
  58. package/dist/workflow-dispatch-settlement.js +140 -0
  59. package/dist/workflow-kind.d.ts +37 -0
  60. package/dist/workflow-kind.js +310 -0
  61. package/dist/workflow-probe-gate.d.ts +214 -0
  62. package/dist/workflow-probe-gate.js +207 -0
  63. package/dist/workflow-run-event-log.d.ts +21 -0
  64. package/dist/workflow-run-event-log.js +51 -0
  65. package/dist/workflow-run-kind.d.ts +440 -0
  66. package/dist/workflow-run-kind.js +2916 -0
  67. package/dist/workflow-run-reader.d.ts +47 -0
  68. package/dist/workflow-run-reader.js +153 -0
  69. package/dist/workflow-run-restore.d.ts +15 -0
  70. package/dist/workflow-run-restore.js +26 -0
  71. package/dist/workflow-source-closure.d.ts +35 -0
  72. package/dist/workflow-source-closure.js +342 -0
  73. package/dist/ws/index.d.ts +3 -0
  74. package/dist/ws/index.js +3 -0
  75. package/dist/ws/sidecar-events.d.ts +222 -0
  76. package/dist/ws/sidecar-events.js +72 -0
  77. package/dist/ws/sidecar-handler.d.ts +305 -0
  78. package/dist/ws/sidecar-handler.js +2537 -0
  79. package/dist/ws/sidecar-token-authenticator.d.ts +17 -0
  80. package/dist/ws/sidecar-token-authenticator.js +81 -0
  81. package/package.json +35 -12
  82. package/src/agent-repo.test.ts +0 -310
  83. package/src/agent-repo.ts +0 -165
  84. package/src/agent-state-kind.test.ts +0 -247
  85. package/src/agent-state-kind.ts +0 -204
  86. package/src/asset-service.test.ts +0 -540
  87. package/src/asset-service.ts +0 -378
  88. package/src/available-skills-stanza.test.ts +0 -87
  89. package/src/available-skills-stanza.ts +0 -47
  90. package/src/credential-push.ts +0 -65
  91. package/src/event-collector-registry.test.ts +0 -73
  92. package/src/event-collector-registry.ts +0 -171
  93. package/src/event-collector.test.ts +0 -1387
  94. package/src/event-collector.ts +0 -424
  95. package/src/hub-session-lookups.ts +0 -206
  96. package/src/hub-session-orchestrator.test.ts +0 -510
  97. package/src/hub-session-orchestrator.ts +0 -213
  98. package/src/index.ts +0 -78
  99. package/src/repo-store/index.ts +0 -15
  100. package/src/repo-store/store.test.ts +0 -1169
  101. package/src/repo-store/store.ts +0 -428
  102. package/src/repo-store/types.ts +0 -253
  103. package/src/session-service.test.ts +0 -895
  104. package/src/session-service.ts +0 -464
  105. package/src/skill-kind.test.ts +0 -599
  106. package/src/skill-kind.ts +0 -350
  107. package/src/ws/index.ts +0 -18
  108. package/src/ws/sidecar-events.test.ts +0 -96
  109. package/src/ws/sidecar-events.ts +0 -231
  110. package/src/ws/sidecar-handler.test.ts +0 -2217
  111. package/src/ws/sidecar-handler.ts +0 -1574
  112. package/tsconfig.json +0 -4
  113. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,222 @@
1
+ import type { PackRejectReason, RepoId, RunGrantsFrame } from "@intx/types/sidecar";
2
+ import type { ApprovalSnapshot, ConnectorThreadState } from "@intx/types/runtime";
3
+ import type { SignalKind } from "@intx/types";
4
+ export type SidecarMailPersistedRow = {
5
+ id: string;
6
+ createdAt: Date;
7
+ direction: "inbound" | "outbound";
8
+ runId: string | null;
9
+ address: string;
10
+ };
11
+ export type SidecarMailPersistedPayload = SidecarMailPersistedRow & {
12
+ raw: Uint8Array;
13
+ };
14
+ /** Authenticated connection scope attached to a workflow-run pack. */
15
+ export type WorkflowRunPackSource = {
16
+ readonly kind: "shared";
17
+ readonly agentAddress: string;
18
+ } | {
19
+ readonly kind: "allocated";
20
+ readonly agentAddress: string;
21
+ readonly allocationId: string;
22
+ readonly anchorRunId: string;
23
+ readonly generation: number;
24
+ };
25
+ /**
26
+ * Outcome of reserving a mail-triggered run's grants. `skip` means the
27
+ * recipient names no workflow deployment; `rejected` means the deployment's
28
+ * stable run is terminal or its requirements cannot be authorized;
29
+ * `materialized` carries the canonical persisted wire rows.
30
+ */
31
+ export type MailTriggeredRunGrantsResult = {
32
+ outcome: "skip";
33
+ } | {
34
+ outcome: "rejected";
35
+ status: 403 | 409;
36
+ code: string;
37
+ message: string;
38
+ } | {
39
+ outcome: "materialized";
40
+ stepGrants: RunGrantsFrame["stepGrants"];
41
+ };
42
+ export type SidecarEventMap = {
43
+ /** Notification. Emitted for every agent.event frame the wire layer
44
+ * decodes. The wire layer also forwards the event to in-process agent
45
+ * subscribers registered via `router.subscribeAgent`; this event is
46
+ * the host-side observation point. */
47
+ "agent.event": {
48
+ agentAddress: string;
49
+ sessionId: string;
50
+ event: unknown;
51
+ };
52
+ /** Notification. Emitted once when a sidecar's connection closes,
53
+ * carrying every address the connection owned -- challenged session
54
+ * addresses and hub-minted workflow-substrate deployment addresses
55
+ * alike -- so lifecycle teardown covers both. */
56
+ "sidecar.disconnect": {
57
+ ownedAddresses: string[];
58
+ /** Present only when the closing socket was the current allocated owner. */
59
+ allocated?: {
60
+ allocationId: string;
61
+ generation: number;
62
+ };
63
+ };
64
+ /** Notification after the exact authenticated allocation generation registers. */
65
+ "sidecar.allocated.connected": {
66
+ allocationId: string;
67
+ generation: number;
68
+ };
69
+ /** Notification. Emitted when a mail.outbound frame from a sidecar
70
+ * names recipients that the wire layer could not deliver locally and
71
+ * could not enqueue for a disconnected agent. The host is free to
72
+ * relay it onto an external transport or drop it. */
73
+ "mail.outbound.undelivered": {
74
+ rawMessage: string;
75
+ recipients: string[];
76
+ };
77
+ /** Notification. Emitted once per row produced by the host's
78
+ * `persistMail` lookup. The wire layer calls `persistMail` to obtain
79
+ * the rows; this event fires for each so subscribers can react
80
+ * per-row (e.g. dispatch a delivered event). */
81
+ "mail.persisted": SidecarMailPersistedPayload;
82
+ /** Notification after a sidecar confirms a mail trigger is in its durable
83
+ * local inbox. For an exclusive worker, `allocated` identifies the exact
84
+ * generation that acknowledged the message. This is not workflow
85
+ * settlement; the Hub retains the payload until the Git claim-check records
86
+ * consumption. */
87
+ "mail.inbound.acknowledged": {
88
+ agentAddress: string;
89
+ messageId: string;
90
+ allocated?: {
91
+ allocationId: string;
92
+ anchorRunId: string;
93
+ generation: number;
94
+ };
95
+ };
96
+ /** Awaited. Emitted when an agent.deploy.ack frame arrives. Rejection
97
+ * fails the pending deploy with the listener's error. */
98
+ "agent.deploy.ack": {
99
+ agentAddress: string;
100
+ publicKey: string;
101
+ allocated?: {
102
+ allocationId: string;
103
+ anchorRunId: string;
104
+ generation: number;
105
+ };
106
+ };
107
+ /** Notification. Emitted when the sidecar reports a change to an
108
+ * agent's connector-thread state. The wire layer caches the state
109
+ * per agent so the host can read it via
110
+ * `router.getConnectorState(agentAddress)`; this event is for hosts
111
+ * that want to observe transitions directly. `connectorState` is
112
+ * `null` when the agent has no active connector thread. */
113
+ "connector.state.changed": {
114
+ agentAddress: string;
115
+ connectorState: ConnectorThreadState | null;
116
+ };
117
+ /** Awaited. Emitted per address after challenge verification
118
+ * succeeds and before the disconnect queue is flushed. Rejection
119
+ * rolls that address back from the routing table; earlier listeners
120
+ * in registration order have already executed and their side effects
121
+ * are not undone. A subsequent reconnect arriving mid-flight may
122
+ * supersede this one, so listeners must be idempotent. */
123
+ "agent.reconnected": {
124
+ agentAddress: string;
125
+ };
126
+ /** Awaited. Emitted per address after the wire layer has confirmed
127
+ * the sidecar's deploy ref is stale relative to the hub's current
128
+ * ref. The listener's job is to push a fresh deploy pack. The wire
129
+ * layer fires this only when staleness is confirmed; subscribing
130
+ * without a `lookupDeployRef` configured on the router will never
131
+ * deliver. */
132
+ "deploy.ref.stale": {
133
+ agentAddress: string;
134
+ };
135
+ };
136
+ export type SidecarEventType = keyof SidecarEventMap;
137
+ export type SidecarEventListener<T extends SidecarEventType> = (payload: SidecarEventMap[T]) => void | Promise<void>;
138
+ export type SidecarEventEmitter = {
139
+ on<T extends SidecarEventType>(type: T, listener: SidecarEventListener<T>): () => void;
140
+ emit<T extends SidecarEventType>(type: T, payload: SidecarEventMap[T]): void;
141
+ emitAndAwait<T extends SidecarEventType>(type: T, payload: SidecarEventMap[T]): Promise<void>;
142
+ /** Number of listeners registered for `type`. Wire-layer callers use
143
+ * this to skip an `await` when nothing is listening, preserving the
144
+ * synchronous scheduling of unconfigured-handler paths. */
145
+ listenerCount(type: SidecarEventType): number;
146
+ };
147
+ export declare function createSidecarEmitter(): SidecarEventEmitter;
148
+ export type SidecarLookups = {
149
+ /** Returns the hex-encoded Ed25519 public key stored for the address,
150
+ * or `null` if the address is unknown. Used during the reconnect
151
+ * challenge to verify the sidecar's signature. */
152
+ lookupPublicKey?: (agentAddress: string) => Promise<string | null>;
153
+ /** Returns the hub's current deploy ref for the address, or `null` if
154
+ * no deploy state is tracked. The wire layer compares this against
155
+ * the sidecar's reported ref during reconnect and emits
156
+ * `deploy.ref.stale` only on mismatch. */
157
+ lookupDeployRef?: (agentAddress: string) => Promise<string | null>;
158
+ /** Persists a delivered outbound mail frame. Returns one row per
159
+ * persisted record; the wire layer attaches `raw` to each row and
160
+ * emits a `mail.persisted` event. */
161
+ persistMail?: (args: {
162
+ senderAddress: string;
163
+ recipients: string[];
164
+ raw: Uint8Array;
165
+ }) => Promise<SidecarMailPersistedRow[]>;
166
+ /** Co-writes the `signal_correlation` routing row and the `approval` row
167
+ * for a suspending workflow agent step, in one transaction. Called from
168
+ * the `signal.correlation.register` frame handler after the wire layer has
169
+ * confirmed the sending sidecar owns `agentAddress`. Idempotent: a
170
+ * redelivered frame (reconnect, workflow-log replay, supervisor restart
171
+ * re-emitting) is a no-op, not an error. The wire layer does not carry
172
+ * `signalName`; the host derives it from `correlationId`. Resolves the
173
+ * tenancy from the workflow deployment the address names. */
174
+ registerSignalCorrelation?: (args: {
175
+ correlationId: string;
176
+ runId: string;
177
+ anchorRunId: string;
178
+ agentAddress: string;
179
+ kind: SignalKind;
180
+ approvalSnapshot: ApprovalSnapshot;
181
+ }) => Promise<void>;
182
+ /** Reserves a mail-triggered workflow run's grants from the receiving
183
+ * deployment's definition, returning a discriminated result the
184
+ * `mail.outbound` handler orders against delivery. Called for each recipient
185
+ * that is a workflow deployment. The `runId` is the deployment's stable
186
+ * address-derived run id.
187
+ *
188
+ * On `materialized`, `stepGrants` are already persisted and the caller sends
189
+ * them ahead of the inbound mail. Reservation is idempotent on the runId, so
190
+ * a redelivered inbound mail neither double-mints nor throws. On `skip` the
191
+ * address names no deployed workflow deployment, so no grants are sent and
192
+ * the mail still forwards. On `rejected` the stable run is terminal or a
193
+ * declared requirement's authority is insufficient; the caller fails the
194
+ * mail closed for that recipient. */
195
+ materializeMailTriggeredRunGrants?: (args: {
196
+ agentAddress: string;
197
+ runId: string;
198
+ }) => Promise<MailTriggeredRunGrantsResult>;
199
+ /** Ingests a received agent-state pack and returns whether the wire
200
+ * layer should ack or reject the pack to the sidecar. `repoId.kind`
201
+ * is `"agent-state"` and `repoId.id` is the run address. The wire
202
+ * layer dispatches on `repoId.kind` against the receive lookups
203
+ * before calling either; this lookup must reject any pack whose
204
+ * `repoId.kind` is not `"agent-state"`. */
205
+ receiveAgentStatePack?: (repoId: RepoId, pack: Uint8Array, ref: string, commitSha: string) => Promise<{
206
+ accepted: true;
207
+ } | {
208
+ accepted: false;
209
+ reason: PackRejectReason;
210
+ }>;
211
+ /** Ingests a received workflow-run pack and returns whether the wire
212
+ * layer should ack or reject the pack to the sidecar. `source` is derived
213
+ * from the authenticated socket, never from the frame. The lookup must
214
+ * revalidate that source against durable deployment/allocation ownership
215
+ * before advancing the Git ref. */
216
+ receiveWorkflowRunPack?: (repoId: RepoId, pack: Uint8Array, ref: string, commitSha: string, source: WorkflowRunPackSource) => Promise<{
217
+ accepted: true;
218
+ } | {
219
+ accepted: false;
220
+ reason: PackRejectReason;
221
+ }>;
222
+ };
@@ -0,0 +1,72 @@
1
+ // Typed event emitter for the sidecar router.
2
+ //
3
+ // The router emits events at the points where wire-layer frame handling
4
+ // completes and a host-side decision or side effect is required. Two
5
+ // emission shapes are exposed:
6
+ //
7
+ // - `emit(type, payload)` — notification semantics. Each listener runs
8
+ // inside its own try/catch; a thrown error is logged and does not
9
+ // affect other listeners or the wire layer. Used for events whose
10
+ // outcome does not feed back into protocol behavior.
11
+ //
12
+ // - `emitAndAwait(type, payload)` — sequential await semantics.
13
+ // Listeners run in registration order; the first rejection propagates
14
+ // to the caller and stops the chain. Used for events whose outcome
15
+ // affects subsequent wire-layer state (e.g. reconnect rollback).
16
+ //
17
+ // The TSDoc on each entry in `SidecarEventMap` records which semantic
18
+ // applies. Mixing the two on a single event is intentional: today's
19
+ // wire layer already has both behaviors, and pretending otherwise
20
+ // would silently change failure handling.
21
+ import { getLogger } from "@intx/log";
22
+ const logger = getLogger(["hub", "ws", "sidecar", "events"]);
23
+ export function createSidecarEmitter() {
24
+ const listeners = {
25
+ "agent.event": new Set(),
26
+ "sidecar.disconnect": new Set(),
27
+ "sidecar.allocated.connected": new Set(),
28
+ "mail.outbound.undelivered": new Set(),
29
+ "mail.persisted": new Set(),
30
+ "mail.inbound.acknowledged": new Set(),
31
+ "agent.deploy.ack": new Set(),
32
+ "agent.reconnected": new Set(),
33
+ "deploy.ref.stale": new Set(),
34
+ "connector.state.changed": new Set(),
35
+ };
36
+ function on(type, listener) {
37
+ listeners[type].add(listener);
38
+ return () => {
39
+ listeners[type].delete(listener);
40
+ };
41
+ }
42
+ function emit(type, payload) {
43
+ const set = listeners[type];
44
+ if (set.size === 0)
45
+ return;
46
+ for (const listener of [...set]) {
47
+ try {
48
+ const result = listener(payload);
49
+ if (result instanceof Promise) {
50
+ result.catch((err) => {
51
+ logger.warn `Listener for ${type} threw: ${err instanceof Error ? err.message : String(err)}`;
52
+ });
53
+ }
54
+ }
55
+ catch (err) {
56
+ logger.warn `Listener for ${type} threw: ${err instanceof Error ? err.message : String(err)}`;
57
+ }
58
+ }
59
+ }
60
+ async function emitAndAwait(type, payload) {
61
+ const set = listeners[type];
62
+ if (set.size === 0)
63
+ return;
64
+ for (const listener of [...set]) {
65
+ await listener(payload);
66
+ }
67
+ }
68
+ function listenerCount(type) {
69
+ return listeners[type].size;
70
+ }
71
+ return { on, emit, emitAndAwait, listenerCount };
72
+ }
@@ -0,0 +1,305 @@
1
+ import type { GrantWalkSnapshot } from "@intx/types";
2
+ import { type AgentDeployFrame, type HubFrame, type RepoId, type RunGrantsFrame, type CredentialDelivery, type WorkflowSourceAssetMount, type WorkflowProjectionDefinition } from "@intx/types/sidecar";
3
+ import type { ConnectorThreadState, HarnessConfig, InferenceSource } from "@intx/types/runtime";
4
+ import type { SidecarCredentialIdentity } from "../sidecar-allocation/contracts.js";
5
+ import type { ToolPackageManifest } from "@intx/types/tool-packages";
6
+ import type { WorkflowDefinitionSource } from "@intx/types/workflow-sources";
7
+ import { type SidecarEventEmitter, type SidecarLookups } from "./sidecar-events.js";
8
+ export type SidecarConnection = {
9
+ sidecarId: string;
10
+ identity: SidecarAuthIdentity;
11
+ agentAddresses: Set<string>;
12
+ workflowAddresses: Set<string>;
13
+ send(frame: HubFrame): void;
14
+ };
15
+ export type SendPackOptions = {
16
+ /**
17
+ * Repo-relative mount path under the sidecar's per-agent workspace.
18
+ * When set, the receiving sidecar materializes the pack as plain
19
+ * files at `<workspaceRoot>/<mountPath>/` and does NOT apply it to
20
+ * the agent's deploy git tree. Absent for agent-state deploy/state
21
+ * packs, which continue to apply to the deploy tree.
22
+ */
23
+ mountPath?: string;
24
+ /**
25
+ * Override the `repoId` emitted on the wire. The agent-state flow
26
+ * defaults to `{ kind: "agent-state", id: agentAddress }`; asset
27
+ * packs must pass the SOURCE asset's id so audit can correlate the
28
+ * pack back to its hub-side origin. Workflow-run restoration uses a
29
+ * dedicated allocation-bound sender that supplies its derived repo id.
30
+ */
31
+ repoId?: RepoId;
32
+ };
33
+ /**
34
+ * Everything a `sendProbe` caller supplies to populate the outbound
35
+ * `workflow.probe.request` frame: where the definition's bytes come from, the
36
+ * frozen dependency closure the hub already resolved, and the
37
+ * `interchange.workflow` entry-module path whose evaluation produces the
38
+ * `WorkflowDefinition`. The `requestId` is minted inside `sendProbe`, not
39
+ * supplied here.
40
+ */
41
+ export type SendProbeArgs = {
42
+ source: WorkflowDefinitionSource;
43
+ closure: ToolPackageManifest;
44
+ entry: string;
45
+ /** Hub assets a `kind:"asset"` closure entry reads from, delivered inline. */
46
+ assets?: WorkflowSourceAssetMount[];
47
+ };
48
+ /**
49
+ * The payload a `sendProbe` promise resolves with, lifted off the sidecar's
50
+ * `workflow.probe.result` frame: the inert needs-surface projection of the
51
+ * probed workflow, the inert grant set derived from it, the un-flattened grant
52
+ * walk snapshot the set is derived from, and the projection's content hash.
53
+ */
54
+ export type WorkflowProbeResult = {
55
+ projection: WorkflowProjectionDefinition;
56
+ grants: string[];
57
+ grantWalkSnapshot: GrantWalkSnapshot;
58
+ wireHash: string;
59
+ };
60
+ export type SidecarRouter = {
61
+ handleOpen(ws: WsHandle): void;
62
+ handleMessage(ws: WsHandle, data: string): void;
63
+ handleClose(ws: WsHandle): void;
64
+ routeMail(agentAddress: string, rawMessage: string, messageId?: string): boolean;
65
+ /**
66
+ * Deliver a run's authorization grants to the sidecar hosting the named
67
+ * deployment-level mail address, ahead of the trigger mail that starts the
68
+ * run. Routes through the same per-address channel as `routeMail`: over the
69
+ * live connection when the deployment is connected, and into the disconnect
70
+ * queue when the deployment dropped in the window before its first
71
+ * reconnect (while its address is still on `agentAddresses`) -- so grants
72
+ * are queued for a disconnected deployment exactly when the trigger mail is,
73
+ * and ride the same reconnect flush. After a challenged reconnect the
74
+ * address moves to `workflowAddresses`, which carries no queue (that
75
+ * generation's in-flight state is reconstructed sidecar-locally); a
76
+ * `run.grants` then has no queue to ride and this returns `false`. Returns
77
+ * `false` whenever the address is unroutable; the caller keeps any stable-run
78
+ * grant reservation so a later first-delivery attempt reuses it.
79
+ */
80
+ sendRunGrants(agentAddress: string, runId: string, stepGrants: RunGrantsFrame["stepGrants"]): boolean;
81
+ /**
82
+ * Returns the current connector-thread state for the named agent, or
83
+ * `null` if the agent has no active connector thread (or if the
84
+ * sidecar has not yet reported any state — e.g. mid-reconnect, before
85
+ * the harness has loaded its context store). The state is cached
86
+ * from `connector.state.changed` frames; callers should treat `null`
87
+ * as "no threading info available" and fall through to whatever
88
+ * default the calling path uses.
89
+ */
90
+ 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
+ sendAgentUndeploy(agentAddress: string, reason: string): Promise<void>;
125
+ 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>;
150
+ sendSyncRequest(agentAddress: string): void;
151
+ /**
152
+ * Deliver a workflow-run signal to the sidecar that hosts the named
153
+ * deployment-level mail address. The sidecar's hub-link routes the
154
+ * frame through its `signalInboundRouter` into the deployment's
155
+ * supervisor, which sends a `signal.deliver` control IPC frame to
156
+ * the workflow-process child. The child commits the resulting
157
+ * `SignalReceived` event through its own substrate -- the single
158
+ * writer of the workflow-run repo on the sidecar side -- so the
159
+ * pack-push pipeline that propagates the commit to the hub never
160
+ * sees a concurrent writer at the same ref.
161
+ *
162
+ * Throws when no sidecar is registered for `agentAddress`; the
163
+ * caller is responsible for ensuring the deployment is live.
164
+ */
165
+ sendSignalDeliver(opts: {
166
+ agentAddress: string;
167
+ runId: string;
168
+ signalName: string;
169
+ signalId: string;
170
+ payload: unknown;
171
+ }): void;
172
+ /**
173
+ * Deliver a workflow-host drain control payload to the sidecar that
174
+ * hosts the named deployment-level mail address. The sidecar's
175
+ * hub-link routes the frame through its `drainInboundRouter` into
176
+ * the deployment's supervisor, which sends a `drain` control IPC
177
+ * frame to the workflow-process child and arms one `drainTimeout`
178
+ * accumulator per in-flight run. Cancel-mode steps abort on the
179
+ * child side; wait-mode steps continue. Accumulators commit a
180
+ * signed `CancelRequested{origin: "supervisor-drain"}` against the
181
+ * workflow-run repo when the deadline expires.
182
+ *
183
+ * Throws when no sidecar is registered for `agentAddress`; the
184
+ * caller is responsible for ensuring the deployment is live.
185
+ */
186
+ sendDrain(opts: {
187
+ agentAddress: string;
188
+ deadlineMs: number;
189
+ }): void;
190
+ subscribeAgent(agentAddress: string, callback: (event: unknown) => void): () => void;
191
+ dispatchAgentEvent(agentAddress: string, event: unknown): void;
192
+ getConnectedSidecars(): string[];
193
+ getRoutableAddresses(): string[];
194
+ /** Typed event emitter for the receiver-dispatch surface. See
195
+ * `sidecar-events.ts` for the event map and emission semantics. */
196
+ events: SidecarEventEmitter;
197
+ };
198
+ /**
199
+ * A verified sidecar-connection identity resolved by an authenticator from
200
+ * the credentials a sidecar presents on the WebSocket handshake. The
201
+ * `sidecarId` is the connection's own trusted id; it is not the untrusted
202
+ * `sidecarId` claimed on the register/reconnect frame, and it carries no
203
+ * tenant scope. Modeled as a discriminated union so a future non-sidecar
204
+ * principal (e.g. an operator user) can be added as an additional arm
205
+ * without changing existing consumers.
206
+ */
207
+ export type SidecarAuthIdentity = SidecarCredentialIdentity;
208
+ export type AllocatedSidecarTarget = {
209
+ readonly allocationId: string;
210
+ readonly generation: number;
211
+ };
212
+ export type SidecarAllocationRouter = {
213
+ /** Advance the in-memory trust boundary before provisioning a generation. */
214
+ 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. */
218
+ isAllocatedSidecarReady(target: AllocatedSidecarTarget): Promise<boolean>;
219
+ /** Check whether the exact generation already hosts its workflow supervisor. */
220
+ isAllocatedWorkflowActive(target: AllocatedSidecarTarget): Promise<boolean>;
221
+ sendAgentDeployToAllocation(target: AllocatedSidecarTarget, agentAddress: string, config: HarnessConfig, workflow?: AgentDeployFrame["workflow"]): Promise<{
222
+ publicKey: string;
223
+ }>;
224
+ sendPackToAllocation(target: AllocatedSidecarTarget, agentAddress: string, pack: Uint8Array, ref: string, commitSha: string, options?: SendPackOptions): Promise<void>;
225
+ /**
226
+ * Restore one Hub-authoritative workflow-run ref onto the exact allocation
227
+ * generation before its deployment address is routed or supervisor spawned.
228
+ */
229
+ sendWorkflowRunPackToAllocation(target: AllocatedSidecarTarget, agentAddress: string, pack: Uint8Array, ref: string, commitSha: string): Promise<void>;
230
+ bindAllocatedStepRoute(target: AllocatedSidecarTarget, stepAddress: string): Promise<void>;
231
+ unbindAllocatedStepRoute(target: AllocatedSidecarTarget, stepAddress: string): void;
232
+ sendProvisionStepToAllocation(target: AllocatedSidecarTarget, agentAddress: string, config: HarnessConfig): Promise<void>;
233
+ /**
234
+ * Deliver one durable workflow trigger to the exact allocation generation.
235
+ * Grants and mail are written to the same websocket in FIFO order. The
236
+ * returned promise proves only that both frames were sent; the sidecar's
237
+ * durable-inbox acknowledgement is surfaced separately through
238
+ * `mail.inbound.acknowledged`.
239
+ */
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. */
242
+ sendSignalDeliverToAllocation(target: AllocatedSidecarTarget, opts: {
243
+ agentAddress: string;
244
+ runId: string;
245
+ signalName: string;
246
+ signalId: string;
247
+ payload: unknown;
248
+ }): Promise<void>;
249
+ };
250
+ /**
251
+ * Resolves the credentials a sidecar presents on the handshake to a
252
+ * verified identity, or `null` when the credentials are not recognized.
253
+ * The claimed `sidecarId` is an unauthenticated hint; the authenticator
254
+ * derives the trusted identity from the `token` and the returned
255
+ * `sidecarId` is what the router keys connection state off of.
256
+ */
257
+ export type SidecarAuthenticator = (claim: {
258
+ sidecarId: string;
259
+ token: string;
260
+ }) => Promise<SidecarAuthIdentity | null>;
261
+ export type SidecarRouterConfig = {
262
+ requestTimeoutMs?: number;
263
+ /** Hex-encoded 32-byte Ed25519 public key for signing deploy commits.
264
+ * Included in agent.deploy frames so sidecars can verify pack signatures. */
265
+ hubPublicKey?: string;
266
+ /** Resolves each register/reconnect handshake to a verified sidecar
267
+ * identity. Required: without it a connection could route on an
268
+ * unverified frame claim. Return null to reject the handshake. */
269
+ authenticateSidecar: SidecarAuthenticator;
270
+ /** Revalidate durable identity at registration and routing boundaries. */
271
+ validateSidecarIdentity?: (identity: SidecarAuthIdentity, use: "registration" | "readiness" | "routing") => Promise<boolean>;
272
+ challengeTimeoutMs?: number;
273
+ /** Timeout for a `sendProbe` round-trip. A probe materializes a workflow's
274
+ * dependency closure and evaluates it on the sidecar, so it can run longer
275
+ * than a routine `sendRequest`; it gets its own timeout rather than sharing
276
+ * the request timeout. */
277
+ probeTimeoutMs?: number;
278
+ disconnectQueueMaxSize?: number;
279
+ disconnectQueueTTLMs?: number;
280
+ pingTimeoutMs?: number;
281
+ /** Interval between redelivery attempts of a connected-window `mail.inbound`
282
+ * the sidecar has not yet acknowledged with `mail.inbound.ack`. */
283
+ mailAckRetryIntervalMs?: number;
284
+ /** Maximum redelivery attempts before the hub stops retrying an un-acked
285
+ * connected-window `mail.inbound`. Bounds the retry so a sidecar that never
286
+ * acks does not accumulate an unbounded timer per delivery. */
287
+ 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. */
298
+ lookups?: SidecarLookups;
299
+ };
300
+ export type WsHandle = {
301
+ send(data: string): void;
302
+ close(): void;
303
+ };
304
+ export declare const DEFAULT_PROBE_TIMEOUT_MS = 60000;
305
+ export declare function createSidecarRouter(config: SidecarRouterConfig): SidecarRouter & SidecarAllocationRouter;