@intx/hub-sessions 0.2.2 → 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 (87) hide show
  1. package/README.md +3 -5
  2. package/dist/agent-repo.d.ts +23 -7
  3. package/dist/agent-repo.js +19 -6
  4. package/dist/agent-state-kind.js +18 -63
  5. package/dist/asset-service.d.ts +1 -20
  6. package/dist/asset-service.js +19 -97
  7. package/dist/committed-source-tree.d.ts +10 -0
  8. package/dist/committed-source-tree.js +35 -0
  9. package/dist/credential-push.d.ts +52 -7
  10. package/dist/credential-push.js +178 -22
  11. package/dist/event-collector-registry.d.ts +3 -2
  12. package/dist/event-collector-registry.js +42 -13
  13. package/dist/event-collector.d.ts +12 -2
  14. package/dist/event-collector.js +45 -4
  15. package/dist/hub-session-lookups.d.ts +125 -7
  16. package/dist/hub-session-lookups.js +541 -86
  17. package/dist/hub-session-orchestrator.d.ts +2 -3
  18. package/dist/hub-session-orchestrator.js +22 -56
  19. package/dist/index.d.ts +19 -9
  20. package/dist/index.js +16 -7
  21. package/dist/reconciliation-scheduler.d.ts +14 -0
  22. package/dist/reconciliation-scheduler.js +55 -0
  23. package/dist/repo-store/index.d.ts +2 -1
  24. package/dist/repo-store/index.js +1 -0
  25. package/dist/repo-store/store.d.ts +1 -1
  26. package/dist/repo-store/store.js +138 -1
  27. package/dist/repo-store/subscribe-kind.d.ts +6 -3
  28. package/dist/repo-store/subscribe-kind.js +42 -77
  29. package/dist/repo-store/types.d.ts +94 -6
  30. package/dist/repo-store/user-principal-gate.d.ts +26 -0
  31. package/dist/repo-store/user-principal-gate.js +78 -0
  32. package/dist/session-service.d.ts +252 -126
  33. package/dist/session-service.js +851 -624
  34. package/dist/sidecar-allocation/capability-policy.d.ts +27 -0
  35. package/dist/sidecar-allocation/capability-policy.js +124 -0
  36. package/dist/sidecar-allocation/contracts.d.ts +101 -0
  37. package/dist/sidecar-allocation/contracts.js +26 -0
  38. package/dist/sidecar-allocation/index.d.ts +5 -0
  39. package/dist/sidecar-allocation/index.js +4 -0
  40. package/dist/sidecar-allocation/operation.d.ts +10 -0
  41. package/dist/sidecar-allocation/operation.js +54 -0
  42. package/dist/sidecar-allocation/plugin-registry.d.ts +24 -0
  43. package/dist/sidecar-allocation/plugin-registry.js +61 -0
  44. package/dist/sidecar-allocation/reconciler.d.ts +54 -0
  45. package/dist/sidecar-allocation/reconciler.js +825 -0
  46. package/dist/skill-kind.js +12 -62
  47. package/dist/substrate.d.ts +3 -3
  48. package/dist/substrate.js +1 -1
  49. package/dist/workflow-allocation-service.d.ts +64 -0
  50. package/dist/workflow-allocation-service.js +554 -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 +42 -0
  56. package/dist/workflow-dispatch-service.js +209 -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 +29 -1
  60. package/dist/workflow-kind.js +143 -139
  61. package/dist/workflow-probe-gate.d.ts +286 -0
  62. package/dist/workflow-probe-gate.js +382 -0
  63. package/dist/workflow-run-kind.d.ts +239 -32
  64. package/dist/workflow-run-kind.js +874 -188
  65. package/dist/workflow-run-reader.d.ts +1 -1
  66. package/dist/workflow-run-reader.js +3 -7
  67. package/dist/workflow-run-restore.d.ts +16 -0
  68. package/dist/workflow-run-restore.js +30 -0
  69. package/dist/workflow-source-closure.d.ts +35 -0
  70. package/dist/workflow-source-closure.js +342 -0
  71. package/dist/workflow-source-pins.d.ts +8 -0
  72. package/dist/workflow-source-pins.js +14 -0
  73. package/dist/ws/index.d.ts +3 -3
  74. package/dist/ws/index.js +2 -2
  75. package/dist/ws/pending-tracker.d.ts +93 -0
  76. package/dist/ws/pending-tracker.js +132 -0
  77. package/dist/ws/sidecar-events.d.ts +139 -37
  78. package/dist/ws/sidecar-events.js +2 -2
  79. package/dist/ws/sidecar-handler.d.ts +218 -60
  80. package/dist/ws/sidecar-handler.js +1713 -732
  81. package/dist/ws/sidecar-handler.test-helpers.d.ts +38 -0
  82. package/dist/ws/sidecar-handler.test-helpers.js +95 -0
  83. package/dist/ws/sidecar-token-authenticator.d.ts +3 -1
  84. package/dist/ws/sidecar-token-authenticator.js +78 -7
  85. package/package.json +14 -13
  86. package/dist/available-skills-stanza.d.ts +0 -21
  87. package/dist/available-skills-stanza.js +0 -32
@@ -0,0 +1,38 @@
1
+ import type { HarnessConfig } from "@intx/types/runtime";
2
+ import { createSidecarRouter, type AllocatedSidecarTarget, type SidecarAuthIdentity, type SidecarRouterConfig, type WsHandle } from "./sidecar-handler.js";
3
+ export declare const TEST_IDENTITY: Extract<SidecarAuthIdentity, {
4
+ kind: "allocated";
5
+ }>;
6
+ export declare const TEST_TARGET: AllocatedSidecarTarget;
7
+ export declare const TEST_CONFIG: HarnessConfig;
8
+ export declare function createMockWs(): WsHandle & {
9
+ sent: string[];
10
+ closed: boolean;
11
+ /**
12
+ * Resolve once `predicate` holds over the frames sent so far, re-checking
13
+ * on each send.
14
+ *
15
+ * Several of the router's paths write to the socket from a fire-and-forget
16
+ * continuation -- the mail redelivery retry is one -- so a test that
17
+ * triggers one has no return value to await. The send is the event.
18
+ */
19
+ awaitSent(predicate: (sent: readonly string[]) => boolean): Promise<void>;
20
+ };
21
+ export declare function createAllocatedRouter(config?: Partial<SidecarRouterConfig>): import("./sidecar-handler.js").SidecarRouter & import("./sidecar-handler.js").SidecarAllocationRouter;
22
+ export declare function connectAllocated(router: ReturnType<typeof createSidecarRouter>, agentAddresses?: string[], frameType?: "register" | "reconnect"): Promise<WsHandle & {
23
+ sent: string[];
24
+ closed: boolean;
25
+ /**
26
+ * Resolve once `predicate` holds over the frames sent so far, re-checking
27
+ * on each send.
28
+ *
29
+ * Several of the router's paths write to the socket from a fire-and-forget
30
+ * continuation -- the mail redelivery retry is one -- so a test that
31
+ * triggers one has no return value to await. The send is the event.
32
+ */
33
+ awaitSent(predicate: (sent: readonly string[]) => boolean): Promise<void>;
34
+ }>;
35
+ export declare function parsedFrames(ws: {
36
+ sent: string[];
37
+ }): unknown[];
38
+ export declare function tick(): Promise<void>;
@@ -0,0 +1,95 @@
1
+ import { createSidecarRouter, } from "./sidecar-handler.js";
2
+ export const TEST_IDENTITY = {
3
+ kind: "allocated",
4
+ sidecarId: "sc-allocated",
5
+ allocationId: "alloc-1",
6
+ tenantId: "tenant-1",
7
+ anchorRunId: "run_anchor",
8
+ workflowRunAddress: "run_anchor@tenant.example",
9
+ generation: 1,
10
+ };
11
+ export const TEST_TARGET = {
12
+ allocationId: TEST_IDENTITY.allocationId,
13
+ generation: TEST_IDENTITY.generation,
14
+ };
15
+ export const TEST_CONFIG = {
16
+ sessionId: "ses-router-test",
17
+ agentId: "workflow",
18
+ tenantId: TEST_IDENTITY.tenantId,
19
+ principalId: "principal-1",
20
+ agentAddress: TEST_IDENTITY.workflowRunAddress,
21
+ systemPrompt: "test",
22
+ tools: [],
23
+ grants: [],
24
+ sources: [
25
+ {
26
+ id: "test-source",
27
+ provider: "test",
28
+ baseURL: "https://api.example.test",
29
+ credentialId: "test-credential",
30
+ model: "test",
31
+ },
32
+ ],
33
+ defaultSource: "test-source",
34
+ };
35
+ export function createMockWs() {
36
+ let waiters = [];
37
+ return {
38
+ sent: [],
39
+ closed: false,
40
+ send(data) {
41
+ this.sent.push(data);
42
+ const waking = waiters;
43
+ waiters = [];
44
+ for (const wake of waking)
45
+ wake();
46
+ },
47
+ close() {
48
+ this.closed = true;
49
+ },
50
+ async awaitSent(predicate) {
51
+ for (;;) {
52
+ // Re-checked on every pass, so a frame sent before this call resolves
53
+ // it rather than leaving it waiting for another send.
54
+ const sent = new Promise((resolve) => {
55
+ waiters.push(resolve);
56
+ });
57
+ if (predicate(this.sent))
58
+ return;
59
+ await sent;
60
+ }
61
+ },
62
+ };
63
+ }
64
+ export function createAllocatedRouter(config = {}) {
65
+ const router = createSidecarRouter({
66
+ authenticateSidecar: async () => TEST_IDENTITY,
67
+ validateSidecarIdentity: async () => true,
68
+ hubPublicKey: "a".repeat(64),
69
+ requestTimeoutMs: 500,
70
+ ...config,
71
+ });
72
+ router.fenceAllocation(TEST_TARGET.allocationId, TEST_TARGET.generation);
73
+ return router;
74
+ }
75
+ export async function connectAllocated(router, agentAddresses = [], frameType = "register") {
76
+ const ws = createMockWs();
77
+ router.handleOpen(ws);
78
+ router.handleMessage(ws, JSON.stringify({
79
+ type: frameType,
80
+ sidecarId: TEST_IDENTITY.sidecarId,
81
+ token: "token",
82
+ agentAddresses,
83
+ }));
84
+ await tick();
85
+ return ws;
86
+ }
87
+ export function parsedFrames(ws) {
88
+ return ws.sent.map((raw) => {
89
+ const parsed = JSON.parse(raw);
90
+ return parsed;
91
+ });
92
+ }
93
+ export function tick() {
94
+ return new Promise((resolve) => setTimeout(resolve, 0));
95
+ }
@@ -1,4 +1,5 @@
1
1
  import type { DB } from "@intx/db";
2
+ import type { SidecarCredentialResolver } from "../sidecar-allocation/contracts.js";
2
3
  import type { SidecarAuthenticator } from "./sidecar-handler.js";
3
4
  export type CreateSidecarTokenAuthenticatorDeps = {
4
5
  db: DB["db"];
@@ -12,4 +13,5 @@ export type CreateSidecarTokenAuthenticatorDeps = {
12
13
  * `sidecarId` on the frame is ignored: identity is derived from the
13
14
  * token alone.
14
15
  */
15
- export declare function createSidecarTokenAuthenticator({ db, }: CreateSidecarTokenAuthenticatorDeps): SidecarAuthenticator;
16
+ export declare function createSidecarCredentialResolver({ db, }: CreateSidecarTokenAuthenticatorDeps): SidecarCredentialResolver;
17
+ export declare function createSidecarTokenAuthenticator(deps: CreateSidecarTokenAuthenticatorDeps): SidecarAuthenticator;
@@ -1,6 +1,6 @@
1
- import { eq } from "drizzle-orm";
1
+ import { and, eq, inArray } from "drizzle-orm";
2
2
  import { sha256 } from "@intx/crypto";
3
- import { sidecar } from "@intx/db/schema";
3
+ import { sidecar, sidecarAllocation, workflowProbe, workflowRun, } from "@intx/db/schema";
4
4
  /**
5
5
  * Builds an authenticator that verifies a sidecar's presented token
6
6
  * against the per-sidecar hash stored on the `sidecar` table. The token
@@ -10,15 +10,86 @@ import { sidecar } from "@intx/db/schema";
10
10
  * `sidecarId` on the frame is ignored: identity is derived from the
11
11
  * token alone.
12
12
  */
13
- export function createSidecarTokenAuthenticator({ db, }) {
14
- return async ({ token }) => {
13
+ export function createSidecarCredentialResolver({ db, }) {
14
+ async function resolve(token) {
15
15
  const tokenHash = await sha256(token);
16
16
  const row = await db.query.sidecar.findFirst({
17
17
  where: eq(sidecar.tokenHashSha256, tokenHash),
18
18
  });
19
- if (row === undefined) {
19
+ if (row === undefined)
20
20
  return null;
21
+ const allocation = await db.query.sidecarAllocation.findFirst({
22
+ where: eq(sidecarAllocation.sidecarId, row.id),
23
+ });
24
+ if (allocation !== undefined) {
25
+ if (allocation.status !== "provisioning" &&
26
+ allocation.status !== "allocated") {
27
+ return null;
28
+ }
29
+ const anchor = await db.query.workflowRun.findFirst({
30
+ columns: { address: true },
31
+ where: eq(workflowRun.id, allocation.anchorRunId),
32
+ });
33
+ if (anchor?.address === null || anchor?.address === undefined)
34
+ return null;
35
+ return {
36
+ kind: "allocated",
37
+ sidecarId: row.id,
38
+ allocationId: allocation.id,
39
+ tenantId: allocation.tenantId,
40
+ anchorRunId: allocation.anchorRunId,
41
+ workflowRunAddress: anchor.address,
42
+ generation: allocation.generation,
43
+ };
44
+ }
45
+ const probe = await db.query.workflowProbe.findFirst({
46
+ where: eq(workflowProbe.sidecarId, row.id),
47
+ });
48
+ if (probe === undefined ||
49
+ (probe.status !== "provisioning" && probe.status !== "probing")) {
50
+ return null;
51
+ }
52
+ return {
53
+ kind: "probe",
54
+ sidecarId: row.id,
55
+ allocationId: probe.id,
56
+ tenantId: probe.tenantId,
57
+ generation: probe.generation,
58
+ };
59
+ }
60
+ async function isCurrent(identity, use) {
61
+ if (identity.kind === "probe") {
62
+ const statuses = use === "registration"
63
+ ? ["provisioning", "probing"]
64
+ : ["probing"];
65
+ const probe = await db.query.workflowProbe.findFirst({
66
+ columns: { id: true },
67
+ where: and(eq(workflowProbe.id, identity.allocationId), eq(workflowProbe.sidecarId, identity.sidecarId), eq(workflowProbe.tenantId, identity.tenantId), eq(workflowProbe.generation, identity.generation), inArray(workflowProbe.status, statuses)),
68
+ });
69
+ return probe !== undefined;
21
70
  }
22
- return { kind: "sidecar", sidecarId: row.id };
23
- };
71
+ const statuses = use === "registration"
72
+ ? ["provisioning", "allocated"]
73
+ : ["allocated"];
74
+ const allocation = await db.query.sidecarAllocation.findFirst({
75
+ where: and(eq(sidecarAllocation.id, identity.allocationId), eq(sidecarAllocation.sidecarId, identity.sidecarId), eq(sidecarAllocation.tenantId, identity.tenantId), eq(sidecarAllocation.anchorRunId, identity.anchorRunId), eq(sidecarAllocation.generation, identity.generation), inArray(sidecarAllocation.status, statuses)),
76
+ });
77
+ if (allocation === undefined)
78
+ return false;
79
+ if (use === "registration")
80
+ return true;
81
+ if (allocation.ensureAcceptedGeneration !== identity.generation) {
82
+ return false;
83
+ }
84
+ const anchor = await db.query.workflowRun.findFirst({
85
+ columns: { address: true },
86
+ where: eq(workflowRun.id, identity.anchorRunId),
87
+ });
88
+ return anchor?.address === identity.workflowRunAddress;
89
+ }
90
+ return { resolve, isCurrent };
91
+ }
92
+ export function createSidecarTokenAuthenticator(deps) {
93
+ const resolver = createSidecarCredentialResolver(deps);
94
+ return async ({ token }) => resolver.resolve(token);
24
95
  }
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@intx/hub-sessions",
3
- "version": "0.2.2",
3
+ "description": "Session-orchestration substrate for the hub: sidecar router, session, and asset services",
4
+ "version": "0.4.0",
4
5
  "license": "LGPL-2.1-only",
5
6
  "type": "module",
6
7
  "exports": {
@@ -16,18 +17,18 @@
16
17
  }
17
18
  },
18
19
  "dependencies": {
19
- "@intx/agent": "0.2.2",
20
- "@intx/crypto": "0.2.2",
21
- "@intx/db": "0.2.2",
22
- "@intx/hub-common": "0.2.2",
23
- "@intx/log": "0.2.2",
24
- "@intx/mime": "0.2.2",
25
- "@intx/pack-transport": "0.2.2",
26
- "@intx/storage-isogit": "0.2.2",
27
- "@intx/tool-packaging": "0.2.2",
28
- "@intx/types": "0.2.2",
29
- "@intx/workflow": "0.2.2",
30
- "@intx/workflow-deploy": "0.2.2",
20
+ "@intx/agent": "0.4.0",
21
+ "@intx/crypto": "0.4.0",
22
+ "@intx/db": "0.4.0",
23
+ "@intx/hub-common": "0.4.0",
24
+ "@intx/log": "0.4.0",
25
+ "@intx/mime": "0.4.0",
26
+ "@intx/pack-transport": "0.4.0",
27
+ "@intx/storage-isogit": "0.4.0",
28
+ "@intx/tool-packaging": "0.4.0",
29
+ "@intx/types": "0.4.0",
30
+ "@intx/workflow": "0.4.0",
31
+ "@intx/workflow-deploy": "0.4.0",
31
32
  "arktype": "^2.1.29",
32
33
  "drizzle-orm": "^0.45.1",
33
34
  "isomorphic-git": "^1.27.2",
@@ -1,21 +0,0 @@
1
- export type AvailableSkillEntry = {
2
- /** Qualified skill identifier in the form `<asset.name>/<skill-name>`. */
3
- qualifiedName: string;
4
- /** SKILL.md frontmatter description, verbatim. */
5
- description: string;
6
- /** Workspace-relative path the agent's `read_file` should target,
7
- * shaped like `workspace/<mountPath>/<skill-name>/`. */
8
- workspacePath: string;
9
- };
10
- /**
11
- * Render the `<available_skills>` stanza appended to the agent's
12
- * system prompt. Returns the empty string when `entries` is empty —
13
- * an empty `<available_skills></available_skills>` wrapper would be
14
- * misleading noise for agents with no skills attached.
15
- *
16
- * Values are XML-escaped at the boundary. The skill kind handler
17
- * already rejects descriptions containing literal `<` or `>` so the
18
- * `&` escape is the practical case in production; the others are
19
- * defensive.
20
- */
21
- export declare function buildAvailableSkillsStanza(entries: AvailableSkillEntry[]): string;
@@ -1,32 +0,0 @@
1
- /**
2
- * Render the `<available_skills>` stanza appended to the agent's
3
- * system prompt. Returns the empty string when `entries` is empty —
4
- * an empty `<available_skills></available_skills>` wrapper would be
5
- * misleading noise for agents with no skills attached.
6
- *
7
- * Values are XML-escaped at the boundary. The skill kind handler
8
- * already rejects descriptions containing literal `<` or `>` so the
9
- * `&` escape is the practical case in production; the others are
10
- * defensive.
11
- */
12
- export function buildAvailableSkillsStanza(entries) {
13
- if (entries.length === 0) {
14
- return "";
15
- }
16
- const lines = ["<available_skills>"];
17
- for (const entry of entries) {
18
- lines.push(" <skill>");
19
- lines.push(` <name>${escapeXml(entry.qualifiedName)}</name>`);
20
- lines.push(` <description>${escapeXml(entry.description)}</description>`);
21
- lines.push(` <path>${escapeXml(entry.workspacePath)}</path>`);
22
- lines.push(" </skill>");
23
- }
24
- lines.push("</available_skills>");
25
- return lines.join("\n");
26
- }
27
- function escapeXml(value) {
28
- return value
29
- .replaceAll("&", "&amp;")
30
- .replaceAll("<", "&lt;")
31
- .replaceAll(">", "&gt;");
32
- }