@intx/hub-sessions 0.1.2 → 0.2.2

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/LICENSE +176 -0
  2. package/README.md +84 -1
  3. package/dist/agent-repo.d.ts +89 -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 +185 -0
  7. package/dist/asset-service.d.ts +123 -0
  8. package/dist/asset-service.js +349 -0
  9. package/dist/available-skills-stanza.d.ts +21 -0
  10. package/dist/available-skills-stanza.js +32 -0
  11. package/dist/credential-push.d.ts +32 -0
  12. package/dist/credential-push.js +85 -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 +357 -0
  17. package/dist/hub-session-lookups.d.ts +17 -0
  18. package/dist/hub-session-lookups.js +204 -0
  19. package/dist/hub-session-orchestrator.d.ts +25 -0
  20. package/dist/hub-session-orchestrator.js +122 -0
  21. package/dist/index.d.ts +18 -0
  22. package/dist/index.js +16 -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 +1692 -0
  29. package/dist/repo-store/subscribe-kind.d.ts +53 -0
  30. package/dist/repo-store/subscribe-kind.js +179 -0
  31. package/dist/repo-store/types.d.ts +483 -0
  32. package/dist/repo-store/types.js +42 -0
  33. package/dist/session-service.d.ts +235 -0
  34. package/dist/session-service.js +997 -0
  35. package/dist/skill-kind.d.ts +41 -0
  36. package/dist/skill-kind.js +288 -0
  37. package/dist/substrate.d.ts +8 -0
  38. package/dist/substrate.js +21 -0
  39. package/dist/workflow-kind.d.ts +21 -0
  40. package/dist/workflow-kind.js +263 -0
  41. package/dist/workflow-run-event-log.d.ts +21 -0
  42. package/dist/workflow-run-event-log.js +51 -0
  43. package/dist/workflow-run-kind.d.ts +326 -0
  44. package/dist/workflow-run-kind.js +2646 -0
  45. package/dist/workflow-run-reader.d.ts +47 -0
  46. package/dist/workflow-run-reader.js +157 -0
  47. package/dist/ws/index.d.ts +3 -0
  48. package/dist/ws/index.js +3 -0
  49. package/dist/ws/sidecar-events.d.ts +134 -0
  50. package/dist/ws/sidecar-events.js +70 -0
  51. package/dist/ws/sidecar-handler.d.ts +184 -0
  52. package/dist/ws/sidecar-handler.js +1603 -0
  53. package/dist/ws/sidecar-token-authenticator.d.ts +15 -0
  54. package/dist/ws/sidecar-token-authenticator.js +24 -0
  55. package/package.json +34 -12
  56. package/src/agent-repo.test.ts +0 -310
  57. package/src/agent-repo.ts +0 -165
  58. package/src/agent-state-kind.test.ts +0 -247
  59. package/src/agent-state-kind.ts +0 -204
  60. package/src/asset-service.test.ts +0 -540
  61. package/src/asset-service.ts +0 -378
  62. package/src/available-skills-stanza.test.ts +0 -87
  63. package/src/available-skills-stanza.ts +0 -47
  64. package/src/credential-push.ts +0 -65
  65. package/src/event-collector-registry.test.ts +0 -73
  66. package/src/event-collector-registry.ts +0 -171
  67. package/src/event-collector.test.ts +0 -1387
  68. package/src/event-collector.ts +0 -424
  69. package/src/hub-session-lookups.ts +0 -206
  70. package/src/hub-session-orchestrator.test.ts +0 -510
  71. package/src/hub-session-orchestrator.ts +0 -213
  72. package/src/index.ts +0 -78
  73. package/src/repo-store/index.ts +0 -15
  74. package/src/repo-store/store.test.ts +0 -1169
  75. package/src/repo-store/store.ts +0 -428
  76. package/src/repo-store/types.ts +0 -253
  77. package/src/session-service.test.ts +0 -895
  78. package/src/session-service.ts +0 -464
  79. package/src/skill-kind.test.ts +0 -599
  80. package/src/skill-kind.ts +0 -350
  81. package/src/ws/index.ts +0 -18
  82. package/src/ws/sidecar-events.test.ts +0 -96
  83. package/src/ws/sidecar-events.ts +0 -231
  84. package/src/ws/sidecar-handler.test.ts +0 -2217
  85. package/src/ws/sidecar-handler.ts +0 -1574
  86. package/tsconfig.json +0 -4
  87. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,15 @@
1
+ import type { DB } from "@intx/db";
2
+ import type { SidecarAuthenticator } from "./sidecar-handler.js";
3
+ export type CreateSidecarTokenAuthenticatorDeps = {
4
+ db: DB["db"];
5
+ };
6
+ /**
7
+ * Builds an authenticator that verifies a sidecar's presented token
8
+ * against the per-sidecar hash stored on the `sidecar` table. The token
9
+ * is hashed with SHA-256 and looked up by its digest; a matching row
10
+ * yields that row's id as the verified identity, and an unknown token
11
+ * resolves to `null` so the handshake is rejected. The claimed
12
+ * `sidecarId` on the frame is ignored: identity is derived from the
13
+ * token alone.
14
+ */
15
+ export declare function createSidecarTokenAuthenticator({ db, }: CreateSidecarTokenAuthenticatorDeps): SidecarAuthenticator;
@@ -0,0 +1,24 @@
1
+ import { eq } from "drizzle-orm";
2
+ import { sha256 } from "@intx/crypto";
3
+ import { sidecar } from "@intx/db/schema";
4
+ /**
5
+ * Builds an authenticator that verifies a sidecar's presented token
6
+ * against the per-sidecar hash stored on the `sidecar` table. The token
7
+ * is hashed with SHA-256 and looked up by its digest; a matching row
8
+ * yields that row's id as the verified identity, and an unknown token
9
+ * resolves to `null` so the handshake is rejected. The claimed
10
+ * `sidecarId` on the frame is ignored: identity is derived from the
11
+ * token alone.
12
+ */
13
+ export function createSidecarTokenAuthenticator({ db, }) {
14
+ return async ({ token }) => {
15
+ const tokenHash = await sha256(token);
16
+ const row = await db.query.sidecar.findFirst({
17
+ where: eq(sidecar.tokenHashSha256, tokenHash),
18
+ });
19
+ if (row === undefined) {
20
+ return null;
21
+ }
22
+ return { kind: "sidecar", sidecarId: row.id };
23
+ };
24
+ }
package/package.json CHANGED
@@ -1,23 +1,45 @@
1
1
  {
2
2
  "name": "@intx/hub-sessions",
3
- "version": "0.1.2",
3
+ "version": "0.2.2",
4
4
  "license": "LGPL-2.1-only",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  ".": {
8
- "types": "./src/index.ts",
9
- "default": "./src/index.ts"
8
+ "intx-src": "./src/index.ts",
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ },
12
+ "./substrate": {
13
+ "intx-src": "./src/substrate.ts",
14
+ "types": "./dist/substrate.d.ts",
15
+ "default": "./dist/substrate.js"
10
16
  }
11
17
  },
12
18
  "dependencies": {
13
- "@intx/crypto-node": "0.0.0",
14
- "@intx/db": "0.0.0",
15
- "@intx/hub-common": "0.0.0",
16
- "@intx/log": "0.0.0",
17
- "@intx/mime": "0.0.0",
18
- "@intx/pack-transport": "0.0.0",
19
- "@intx/storage-isogit": "0.0.0",
20
- "@intx/types": "0.0.0",
21
- "arktype": "^2.1.29"
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",
31
+ "arktype": "^2.1.29",
32
+ "drizzle-orm": "^0.45.1",
33
+ "isomorphic-git": "^1.27.2",
34
+ "tar": "^7.5.1"
35
+ },
36
+ "files": [
37
+ "dist",
38
+ "README.md",
39
+ "LICENSE"
40
+ ],
41
+ "sideEffects": false,
42
+ "publishConfig": {
43
+ "access": "public"
22
44
  }
23
45
  }
@@ -1,310 +0,0 @@
1
- import { describe, test, expect, afterAll, beforeAll } from "bun:test";
2
- import fs from "node:fs";
3
- import os from "node:os";
4
- import path from "node:path";
5
- import git from "isomorphic-git";
6
- import { generateKeyPair } from "@intx/crypto-node";
7
- import { createDeployPack } from "@intx/storage-isogit";
8
- import { createAgentRepoStore } from "./agent-repo";
9
- import type { KeyPair } from "@intx/types/runtime";
10
-
11
- const tempDirs: string[] = [];
12
-
13
- async function makeTempDir(prefix: string): Promise<string> {
14
- const d = await fs.promises.mkdtemp(path.join(os.tmpdir(), prefix));
15
- tempDirs.push(d);
16
- return d;
17
- }
18
-
19
- let signingKey: KeyPair;
20
-
21
- beforeAll(async () => {
22
- signingKey = await generateKeyPair();
23
- });
24
-
25
- afterAll(async () => {
26
- for (const d of tempDirs.splice(0)) {
27
- await fs.promises.rm(d, { recursive: true, force: true }).catch((_e) => {
28
- /* best effort cleanup */
29
- });
30
- }
31
- });
32
-
33
- describe("AgentRepoStore", () => {
34
- test("writeDeployTree creates a commit with the system prompt", async () => {
35
- const dataDir = await makeTempDir("agent-repo-");
36
- const store = createAgentRepoStore({ dataDir, signingKey });
37
-
38
- const { commitSha } = await store.writeDeployTree("agent-1", {
39
- systemPrompt: "You are a test agent.",
40
- });
41
-
42
- expect(commitSha).toMatch(/^[0-9a-f]{40}$/);
43
-
44
- const repoDir = path.join(dataDir, "agents", "agent-1");
45
- const prompt = await fs.promises.readFile(
46
- path.join(repoDir, "deploy", "prompt.md"),
47
- "utf-8",
48
- );
49
- expect(prompt).toBe("You are a test agent.");
50
-
51
- const ref = await git.resolveRef({
52
- fs,
53
- dir: repoDir,
54
- ref: "refs/heads/deploy",
55
- });
56
- expect(ref).toBe(commitSha);
57
- });
58
-
59
- test("writeDeployTree does not advance refs/heads/main", async () => {
60
- const dataDir = await makeTempDir("agent-repo-ref-");
61
- const store = createAgentRepoStore({ dataDir, signingKey });
62
-
63
- await store.writeDeployTree("agent-ref", {
64
- systemPrompt: "Ref test.",
65
- });
66
-
67
- const repoDir = path.join(dataDir, "agents", "agent-ref");
68
- const mainLog = await git.log({ fs, dir: repoDir, ref: "refs/heads/main" });
69
- const deployLog = await git.log({
70
- fs,
71
- dir: repoDir,
72
- ref: "refs/heads/deploy",
73
- });
74
-
75
- // main should only have the init commit
76
- expect(mainLog.length).toBe(1);
77
- // deploy should have 2: init parent + deploy commit
78
- expect(deployLog.length).toBe(2);
79
- });
80
-
81
- test("createDeployPack produces a valid packfile", async () => {
82
- const dataDir = await makeTempDir("agent-repo-pack-");
83
- const store = createAgentRepoStore({ dataDir, signingKey });
84
-
85
- await store.writeDeployTree("agent-2", {
86
- systemPrompt: "Pack test.",
87
- });
88
-
89
- const { pack, commitSha, ref } = await store.createDeployPack("agent-2");
90
-
91
- expect(pack).toBeInstanceOf(Uint8Array);
92
- expect(pack.length).toBeGreaterThan(0);
93
- expect(commitSha).toMatch(/^[0-9a-f]{40}$/);
94
- expect(ref).toBe("refs/heads/deploy");
95
- });
96
-
97
- test("receiveStatePack indexes objects and updates ref", async () => {
98
- const dataDir = await makeTempDir("agent-repo-state-");
99
- const store = createAgentRepoStore({ dataDir, signingKey });
100
-
101
- await store.writeDeployTree("agent-3", {
102
- systemPrompt: "State test.",
103
- });
104
-
105
- const sourceDir = await makeTempDir("state-source-");
106
- await git.init({ fs, dir: sourceDir, defaultBranch: "main" });
107
- await fs.promises.mkdir(path.join(sourceDir, "state"), { recursive: true });
108
- await fs.promises.writeFile(
109
- path.join(sourceDir, "state", "turns.jsonl"),
110
- '{"messages":[]}',
111
- );
112
- await git.add({ fs, dir: sourceDir, filepath: "state/turns.jsonl" });
113
- const stateCommit = await git.commit({
114
- fs,
115
- dir: sourceDir,
116
- message: "State snapshot",
117
- author: { name: "test", email: "test@test" },
118
- });
119
-
120
- const { pack } = await createDeployPack(sourceDir, "refs/heads/main");
121
-
122
- const stateRef = "refs/instances/test-instance";
123
- await store.receiveStatePack(
124
- { kind: "agent-state", id: "agent-3" },
125
- pack,
126
- stateRef,
127
- stateCommit,
128
- );
129
-
130
- const repoDir = path.join(dataDir, "agents", "agent-3");
131
- const resolved = await git.resolveRef({
132
- fs,
133
- dir: repoDir,
134
- ref: stateRef,
135
- });
136
- expect(resolved).toBe(stateCommit);
137
- });
138
-
139
- test("receiveStatePack accepts packs with .gitignore alongside state", async () => {
140
- const dataDir = await makeTempDir("agent-repo-gitignore-");
141
- const store = createAgentRepoStore({ dataDir, signingKey });
142
-
143
- await store.writeDeployTree("agent-gi", {
144
- systemPrompt: "Gitignore test.",
145
- });
146
-
147
- const sourceDir = await makeTempDir("gitignore-source-");
148
- await git.init({ fs, dir: sourceDir, defaultBranch: "main" });
149
- await fs.promises.writeFile(path.join(sourceDir, ".gitignore"), "keys/\n");
150
- await fs.promises.mkdir(path.join(sourceDir, "state"), { recursive: true });
151
- await fs.promises.writeFile(
152
- path.join(sourceDir, "state", "turns.jsonl"),
153
- "{}",
154
- );
155
- await git.add({ fs, dir: sourceDir, filepath: ".gitignore" });
156
- await git.add({ fs, dir: sourceDir, filepath: "state/turns.jsonl" });
157
- const stateCommit = await git.commit({
158
- fs,
159
- dir: sourceDir,
160
- message: "State with gitignore",
161
- author: { name: "test", email: "test@test" },
162
- });
163
-
164
- const { pack } = await createDeployPack(sourceDir, "refs/heads/main");
165
- const stateRef = "refs/instances/gi-test";
166
- await store.receiveStatePack(
167
- { kind: "agent-state", id: "agent-gi" },
168
- pack,
169
- stateRef,
170
- stateCommit,
171
- );
172
-
173
- const repoDir = path.join(dataDir, "agents", "agent-gi");
174
- const resolved = await git.resolveRef({
175
- fs,
176
- dir: repoDir,
177
- ref: stateRef,
178
- });
179
- expect(resolved).toBe(stateCommit);
180
- });
181
-
182
- test("receiveStatePack rejects packs with only .gitignore and no state/", async () => {
183
- const dataDir = await makeTempDir("agent-repo-gitignore-only-");
184
- const store = createAgentRepoStore({ dataDir, signingKey });
185
-
186
- await store.writeDeployTree("agent-gio", {
187
- systemPrompt: "Gitignore-only test.",
188
- });
189
-
190
- const sourceDir = await makeTempDir("gitignore-only-source-");
191
- await git.init({ fs, dir: sourceDir, defaultBranch: "main" });
192
- await fs.promises.writeFile(path.join(sourceDir, ".gitignore"), "keys/\n");
193
- await git.add({ fs, dir: sourceDir, filepath: ".gitignore" });
194
- const badCommit = await git.commit({
195
- fs,
196
- dir: sourceDir,
197
- message: "Only gitignore",
198
- author: { name: "test", email: "test@test" },
199
- });
200
-
201
- const { pack } = await createDeployPack(sourceDir, "refs/heads/main");
202
-
203
- await expect(
204
- store.receiveStatePack(
205
- { kind: "agent-state", id: "agent-gio" },
206
- pack,
207
- "refs/instances/test",
208
- badCommit,
209
- ),
210
- ).rejects.toThrow("path_violation");
211
- });
212
-
213
- test("receiveStatePack rejects packs with paths outside state/", async () => {
214
- const dataDir = await makeTempDir("agent-repo-confined-");
215
- const store = createAgentRepoStore({ dataDir, signingKey });
216
-
217
- await store.writeDeployTree("agent-confined", {
218
- systemPrompt: "Confinement test.",
219
- });
220
-
221
- const sourceDir = await makeTempDir("confined-source-");
222
- await git.init({ fs, dir: sourceDir, defaultBranch: "main" });
223
- await fs.promises.mkdir(path.join(sourceDir, "state"), { recursive: true });
224
- await fs.promises.mkdir(path.join(sourceDir, "deploy"), {
225
- recursive: true,
226
- });
227
- await fs.promises.writeFile(
228
- path.join(sourceDir, "state", "turns.jsonl"),
229
- "{}",
230
- );
231
- await fs.promises.writeFile(
232
- path.join(sourceDir, "deploy", "prompt.md"),
233
- "evil",
234
- );
235
- await git.add({ fs, dir: sourceDir, filepath: "state/turns.jsonl" });
236
- await git.add({ fs, dir: sourceDir, filepath: "deploy/prompt.md" });
237
- const badCommit = await git.commit({
238
- fs,
239
- dir: sourceDir,
240
- message: "Escaped confinement",
241
- author: { name: "test", email: "test@test" },
242
- });
243
-
244
- const { pack } = await createDeployPack(sourceDir, "refs/heads/main");
245
-
246
- await expect(
247
- store.receiveStatePack(
248
- { kind: "agent-state", id: "agent-confined" },
249
- pack,
250
- "refs/instances/test",
251
- badCommit,
252
- ),
253
- ).rejects.toThrow("path_violation");
254
- });
255
-
256
- test("writeDeployTree produces a fresh commit when content changes", async () => {
257
- const dataDir = await makeTempDir("agent-repo-idem-");
258
- const store = createAgentRepoStore({ dataDir, signingKey });
259
-
260
- const first = await store.writeDeployTree("agent-4", {
261
- systemPrompt: "Version 1",
262
- });
263
-
264
- const second = await store.writeDeployTree("agent-4", {
265
- systemPrompt: "Version 2",
266
- });
267
-
268
- expect(second.commitSha).not.toBe(first.commitSha);
269
-
270
- const repoDir = path.join(dataDir, "agents", "agent-4");
271
- const prompt = await fs.promises.readFile(
272
- path.join(repoDir, "deploy", "prompt.md"),
273
- "utf-8",
274
- );
275
- expect(prompt).toBe("Version 2");
276
- });
277
-
278
- test("hub repo does not contain state/ scaffolding", async () => {
279
- const dataDir = await makeTempDir("agent-repo-nostate-");
280
- const store = createAgentRepoStore({ dataDir, signingKey });
281
-
282
- await store.writeDeployTree("agent-5", {
283
- systemPrompt: "No state test.",
284
- });
285
-
286
- const repoDir = path.join(dataDir, "agents", "agent-5");
287
- const stateExists = await fs.promises
288
- .stat(path.join(repoDir, "state"))
289
- .then(() => true)
290
- .catch(() => false);
291
- expect(stateExists).toBe(false);
292
- });
293
-
294
- test("rejects agent IDs with path traversal characters", () => {
295
- const dataDir = "/tmp/never-created";
296
- const store = createAgentRepoStore({ dataDir, signingKey });
297
-
298
- expect(() =>
299
- store.writeDeployTree("../../evil", {
300
- systemPrompt: "x",
301
- }),
302
- ).toThrow("repo_id_invalid: ../../evil");
303
-
304
- expect(() =>
305
- store.writeDeployTree("agent@domain", {
306
- systemPrompt: "x",
307
- }),
308
- ).toThrow("repo_id_invalid: agent@domain");
309
- });
310
- });
package/src/agent-repo.ts DELETED
@@ -1,165 +0,0 @@
1
- import { createSSHSignature } from "@intx/crypto-node";
2
-
3
- import { createRepoStore } from "./repo-store";
4
- import type { AuthorizeFn, RepoId, RepoStore } from "./repo-store";
5
- import {
6
- agentStateKindHandler,
7
- agentStateAuthorize,
8
- AGENT_STATE_DEPLOY_REF,
9
- type AgentStateHubPrincipal,
10
- type AgentStateSidecarPrincipal,
11
- } from "./agent-state-kind";
12
- import { skillKindHandler, skillAuthorize } from "./skill-kind";
13
-
14
- export type DeployContent = {
15
- systemPrompt: string;
16
- };
17
-
18
- export type AgentRepoStore = {
19
- /**
20
- * Write deploy content into the agent's hub-side repo and commit on
21
- * refs/heads/deploy. Creates the repo if it doesn't exist.
22
- *
23
- * The caller is responsible for serializing calls per agent.
24
- */
25
- writeDeployTree(
26
- agentId: string,
27
- content: DeployContent,
28
- ): Promise<{ commitSha: string }>;
29
-
30
- /**
31
- * Produce a packfile from the agent's current deploy ref.
32
- */
33
- createDeployPack(
34
- agentId: string,
35
- ): Promise<{ pack: Uint8Array; commitSha: string; ref: string }>;
36
-
37
- /**
38
- * Receive and store a state pack from a sidecar. Indexes the pack
39
- * objects and updates the ref without materializing a working tree.
40
- *
41
- * `repoId.kind` must be `"agent-state"` — this store is per-agent and
42
- * does not generalize across kinds. The `repoId.id` is used as the
43
- * agent address internally.
44
- */
45
- receiveStatePack(
46
- repoId: RepoId,
47
- pack: Uint8Array,
48
- ref: string,
49
- commitSha: string,
50
- ): Promise<void>;
51
-
52
- /** Resolve the current deploy ref SHA, or null if no deploy exists. */
53
- getDeployRef(agentId: string): Promise<string | null>;
54
-
55
- /** Raw 32-byte Ed25519 public key used to sign deploy commits. */
56
- getSigningPublicKey(): Uint8Array;
57
-
58
- /**
59
- * Underlying kind-keyed substrate. Exposed so callers that need to
60
- * operate on non-agent-state kinds (e.g. the asset service writing
61
- * skill repos) can share the same on-disk root and signing key
62
- * without spinning up a parallel RepoStore.
63
- */
64
- readonly repoStore: RepoStore;
65
- };
66
-
67
- export function createAgentRepoStore(config: {
68
- dataDir: string;
69
- signingKey: { privateKey: Uint8Array; publicKey: Uint8Array };
70
- }): AgentRepoStore {
71
- const { dataDir, signingKey } = config;
72
-
73
- const authorize: AuthorizeFn = (principal, incomingRepoId, ref, action) => {
74
- switch (incomingRepoId.kind) {
75
- case "agent-state":
76
- return agentStateAuthorize(principal, incomingRepoId, ref, action);
77
- case "skill":
78
- return skillAuthorize(principal, incomingRepoId, ref, action);
79
- default: {
80
- const _exhaustive: never = incomingRepoId.kind;
81
- return {
82
- allowed: false,
83
- reason: `no authorize registered for kind: ${String(_exhaustive)}`,
84
- };
85
- }
86
- }
87
- };
88
-
89
- // The substrate's signingCallback bridges the agent-repo store's
90
- // raw Ed25519 keypair to the storage layer's per-payload SSHSIG
91
- // signer. Skill asset genesis commits and agent-state deploy
92
- // commits both flow through this signer so that signed commits
93
- // round-trip through the smart-HTTP layer and verify under
94
- // `git log --show-signature` and `git verify-commit`.
95
- const signer = async (payload: string) =>
96
- createSSHSignature(payload, signingKey.privateKey, signingKey.publicKey);
97
-
98
- const store = createRepoStore({
99
- dataDir,
100
- signingKey,
101
- handlers: {
102
- "agent-state": agentStateKindHandler,
103
- skill: skillKindHandler,
104
- },
105
- authorize,
106
- signingCallback: () => signer,
107
- });
108
-
109
- const hub: AgentStateHubPrincipal = { kind: "hub" };
110
-
111
- function repoId(agentId: string): RepoId {
112
- return { kind: "agent-state", id: agentId };
113
- }
114
-
115
- return {
116
- async writeDeployTree(agentId, content) {
117
- const id = repoId(agentId);
118
- const files: Record<string, string> = {
119
- "deploy/prompt.md": content.systemPrompt,
120
- };
121
- return store.writeTree(hub, id, AGENT_STATE_DEPLOY_REF, {
122
- files,
123
- clearPrefix: "deploy/",
124
- message: "Update deploy tree",
125
- });
126
- },
127
-
128
- async createDeployPack(agentId) {
129
- return store.createPack(hub, repoId(agentId), AGENT_STATE_DEPLOY_REF);
130
- },
131
-
132
- async receiveStatePack(incomingRepoId, pack, ref, commitSha) {
133
- if (incomingRepoId.kind !== "agent-state") {
134
- throw new Error(
135
- `AgentRepoStore.receiveStatePack requires repoId.kind === "agent-state", got ${JSON.stringify(incomingRepoId.kind)}`,
136
- );
137
- }
138
- const agentId = incomingRepoId.id;
139
- const id = repoId(agentId);
140
- const principal: AgentStateSidecarPrincipal = {
141
- kind: "sidecar",
142
- agentId,
143
- };
144
- const expectedOldSha = await store.resolveRef(principal, id, ref);
145
- await store.receivePack(
146
- principal,
147
- id,
148
- ref,
149
- pack,
150
- commitSha,
151
- expectedOldSha,
152
- );
153
- },
154
-
155
- async getDeployRef(agentId) {
156
- return store.resolveRef(hub, repoId(agentId), AGENT_STATE_DEPLOY_REF);
157
- },
158
-
159
- getSigningPublicKey() {
160
- return signingKey.publicKey;
161
- },
162
-
163
- repoStore: store,
164
- };
165
- }