@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.
- package/LICENSE +176 -0
- package/README.md +82 -1
- package/dist/agent-repo.d.ts +93 -0
- package/dist/agent-repo.js +109 -0
- package/dist/agent-state-kind.d.ts +12 -0
- package/dist/agent-state-kind.js +189 -0
- package/dist/asset-service.d.ts +104 -0
- package/dist/asset-service.js +267 -0
- package/dist/committed-source-tree.d.ts +10 -0
- package/dist/committed-source-tree.js +35 -0
- package/dist/credential-push.d.ts +33 -0
- package/dist/credential-push.js +109 -0
- package/dist/event-collector-registry.d.ts +20 -0
- package/dist/event-collector-registry.js +115 -0
- package/dist/event-collector.d.ts +39 -0
- package/dist/event-collector.js +365 -0
- package/dist/hub-session-lookups.d.ts +135 -0
- package/dist/hub-session-lookups.js +663 -0
- package/dist/hub-session-orchestrator.d.ts +25 -0
- package/dist/hub-session-orchestrator.js +87 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +24 -0
- package/dist/package-registry-kind.d.ts +70 -0
- package/dist/package-registry-kind.js +260 -0
- package/dist/repo-store/index.d.ts +4 -0
- package/dist/repo-store/index.js +3 -0
- package/dist/repo-store/store.d.ts +41 -0
- package/dist/repo-store/store.js +1829 -0
- package/dist/repo-store/subscribe-kind.d.ts +56 -0
- package/dist/repo-store/subscribe-kind.js +144 -0
- package/dist/repo-store/types.d.ts +571 -0
- package/dist/repo-store/types.js +42 -0
- package/dist/session-service.d.ts +416 -0
- package/dist/session-service.js +1191 -0
- package/dist/sidecar-allocation/contracts.d.ts +78 -0
- package/dist/sidecar-allocation/contracts.js +21 -0
- package/dist/sidecar-allocation/index.d.ts +4 -0
- package/dist/sidecar-allocation/index.js +3 -0
- package/dist/sidecar-allocation/placement-policy.d.ts +11 -0
- package/dist/sidecar-allocation/placement-policy.js +21 -0
- package/dist/sidecar-allocation/plugin-registry.d.ts +11 -0
- package/dist/sidecar-allocation/plugin-registry.js +37 -0
- package/dist/sidecar-allocation/reconciler.d.ts +42 -0
- package/dist/sidecar-allocation/reconciler.js +431 -0
- package/dist/skill-kind.d.ts +41 -0
- package/dist/skill-kind.js +292 -0
- package/dist/substrate.d.ts +8 -0
- package/dist/substrate.js +21 -0
- package/dist/workflow-allocation-service.d.ts +58 -0
- package/dist/workflow-allocation-service.js +239 -0
- package/dist/workflow-closure-resolution.d.ts +106 -0
- package/dist/workflow-closure-resolution.js +123 -0
- package/dist/workflow-definition-ensure.d.ts +24 -0
- package/dist/workflow-definition-ensure.js +75 -0
- package/dist/workflow-dispatch-service.d.ts +40 -0
- package/dist/workflow-dispatch-service.js +146 -0
- package/dist/workflow-dispatch-settlement.d.ts +29 -0
- package/dist/workflow-dispatch-settlement.js +140 -0
- package/dist/workflow-kind.d.ts +37 -0
- package/dist/workflow-kind.js +310 -0
- package/dist/workflow-probe-gate.d.ts +214 -0
- package/dist/workflow-probe-gate.js +207 -0
- package/dist/workflow-run-event-log.d.ts +21 -0
- package/dist/workflow-run-event-log.js +51 -0
- package/dist/workflow-run-kind.d.ts +440 -0
- package/dist/workflow-run-kind.js +2916 -0
- package/dist/workflow-run-reader.d.ts +47 -0
- package/dist/workflow-run-reader.js +153 -0
- package/dist/workflow-run-restore.d.ts +15 -0
- package/dist/workflow-run-restore.js +26 -0
- package/dist/workflow-source-closure.d.ts +35 -0
- package/dist/workflow-source-closure.js +342 -0
- package/dist/ws/index.d.ts +3 -0
- package/dist/ws/index.js +3 -0
- package/dist/ws/sidecar-events.d.ts +222 -0
- package/dist/ws/sidecar-events.js +72 -0
- package/dist/ws/sidecar-handler.d.ts +305 -0
- package/dist/ws/sidecar-handler.js +2537 -0
- package/dist/ws/sidecar-token-authenticator.d.ts +17 -0
- package/dist/ws/sidecar-token-authenticator.js +81 -0
- package/package.json +35 -12
- package/src/agent-repo.test.ts +0 -310
- package/src/agent-repo.ts +0 -165
- package/src/agent-state-kind.test.ts +0 -247
- package/src/agent-state-kind.ts +0 -204
- package/src/asset-service.test.ts +0 -540
- package/src/asset-service.ts +0 -378
- package/src/available-skills-stanza.test.ts +0 -87
- package/src/available-skills-stanza.ts +0 -47
- package/src/credential-push.ts +0 -65
- package/src/event-collector-registry.test.ts +0 -73
- package/src/event-collector-registry.ts +0 -171
- package/src/event-collector.test.ts +0 -1387
- package/src/event-collector.ts +0 -424
- package/src/hub-session-lookups.ts +0 -206
- package/src/hub-session-orchestrator.test.ts +0 -510
- package/src/hub-session-orchestrator.ts +0 -213
- package/src/index.ts +0 -78
- package/src/repo-store/index.ts +0 -15
- package/src/repo-store/store.test.ts +0 -1169
- package/src/repo-store/store.ts +0 -428
- package/src/repo-store/types.ts +0 -253
- package/src/session-service.test.ts +0 -895
- package/src/session-service.ts +0 -464
- package/src/skill-kind.test.ts +0 -599
- package/src/skill-kind.ts +0 -350
- package/src/ws/index.ts +0 -18
- package/src/ws/sidecar-events.test.ts +0 -96
- package/src/ws/sidecar-events.ts +0 -231
- package/src/ws/sidecar-handler.test.ts +0 -2217
- package/src/ws/sidecar-handler.ts +0 -1574
- package/tsconfig.json +0 -4
- 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;
|