@kb-labs/adapters 0.5.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 (276) hide show
  1. package/.cursorrules +32 -0
  2. package/.github/workflows/ci.yml +13 -0
  3. package/.github/workflows/deploy.yml +28 -0
  4. package/.github/workflows/docker-build.yml +25 -0
  5. package/.github/workflows/drift-check.yml +10 -0
  6. package/.github/workflows/profiles-validate.yml +16 -0
  7. package/.github/workflows/release.yml +8 -0
  8. package/.kb/devkit/agents/devkit-maintainer/context.globs +15 -0
  9. package/.kb/devkit/agents/devkit-maintainer/permissions.yml +17 -0
  10. package/.kb/devkit/agents/devkit-maintainer/prompt.md +28 -0
  11. package/.kb/devkit/agents/devkit-maintainer/runbook.md +31 -0
  12. package/.kb/devkit/agents/docs-crafter/prompt.md +24 -0
  13. package/.kb/devkit/agents/docs-crafter/runbook.md +18 -0
  14. package/.kb/devkit/agents/release-manager/context.globs +7 -0
  15. package/.kb/devkit/agents/release-manager/prompt.md +27 -0
  16. package/.kb/devkit/agents/release-manager/runbook.md +17 -0
  17. package/.kb/devkit/agents/test-generator/context.globs +7 -0
  18. package/.kb/devkit/agents/test-generator/prompt.md +27 -0
  19. package/.kb/devkit/agents/test-generator/runbook.md +18 -0
  20. package/CONTRIBUTING.md +90 -0
  21. package/IMPLEMENTATION_COMPLETE.md +416 -0
  22. package/LICENSE +186 -0
  23. package/README-TEMPLATE.md +179 -0
  24. package/README.md +306 -0
  25. package/docs/DOCUMENTATION.md +74 -0
  26. package/docs/adr/0000-template.md +49 -0
  27. package/docs/adr/0001-architecture-and-repository-layout.md +33 -0
  28. package/docs/adr/0002-plugins-and-extensibility.md +46 -0
  29. package/docs/adr/0003-package-and-module-boundaries.md +37 -0
  30. package/docs/adr/0004-versioning-and-release-policy.md +38 -0
  31. package/docs/adr/0005-use-devkit-for-shared-tooling.md +48 -0
  32. package/docs/adr/0006-adopt-devkit-sync.md +47 -0
  33. package/docs/adr/0007-drift-kit-check.md +72 -0
  34. package/docs/adr/0008-devkit-sync-wrapper-strategy.md +67 -0
  35. package/docs/naming-convention.md +272 -0
  36. package/eslint.config.js +27 -0
  37. package/kb-labs.config.json +5 -0
  38. package/package.json +84 -0
  39. package/package.json.bin +25 -0
  40. package/package.json.lib +30 -0
  41. package/packages/adapters-analytics-duckdb/package.json +54 -0
  42. package/packages/adapters-analytics-duckdb/scripts/migrate-from-jsonl.mjs +253 -0
  43. package/packages/adapters-analytics-duckdb/src/index.ts +380 -0
  44. package/packages/adapters-analytics-duckdb/src/manifest.ts +36 -0
  45. package/packages/adapters-analytics-duckdb/src/schema.ts +161 -0
  46. package/packages/adapters-analytics-duckdb/tsconfig.build.json +15 -0
  47. package/packages/adapters-analytics-duckdb/tsconfig.json +9 -0
  48. package/packages/adapters-analytics-duckdb/tsup.config.ts +9 -0
  49. package/packages/adapters-analytics-file/README.md +32 -0
  50. package/packages/adapters-analytics-file/eslint.config.js +27 -0
  51. package/packages/adapters-analytics-file/package.json +50 -0
  52. package/packages/adapters-analytics-file/src/__tests__/daily-stats.spec.ts +287 -0
  53. package/packages/adapters-analytics-file/src/__tests__/scoped-analytics.test.ts +233 -0
  54. package/packages/adapters-analytics-file/src/index.test.ts +214 -0
  55. package/packages/adapters-analytics-file/src/index.ts +830 -0
  56. package/packages/adapters-analytics-file/src/manifest.ts +45 -0
  57. package/packages/adapters-analytics-file/tsconfig.build.json +15 -0
  58. package/packages/adapters-analytics-file/tsconfig.json +9 -0
  59. package/packages/adapters-analytics-file/tsup.config.ts +9 -0
  60. package/packages/adapters-analytics-sqlite/package.json +55 -0
  61. package/packages/adapters-analytics-sqlite/scripts/migrate-from-jsonl.mjs +194 -0
  62. package/packages/adapters-analytics-sqlite/src/index.ts +460 -0
  63. package/packages/adapters-analytics-sqlite/src/manifest.ts +41 -0
  64. package/packages/adapters-analytics-sqlite/tsconfig.build.json +15 -0
  65. package/packages/adapters-analytics-sqlite/tsconfig.json +9 -0
  66. package/packages/adapters-analytics-sqlite/tsup.config.ts +9 -0
  67. package/packages/adapters-environment-docker/README.md +28 -0
  68. package/packages/adapters-environment-docker/eslint.config.js +5 -0
  69. package/packages/adapters-environment-docker/package.json +49 -0
  70. package/packages/adapters-environment-docker/src/index.test.ts +138 -0
  71. package/packages/adapters-environment-docker/src/index.ts +439 -0
  72. package/packages/adapters-environment-docker/src/manifest.ts +65 -0
  73. package/packages/adapters-environment-docker/tsconfig.build.json +15 -0
  74. package/packages/adapters-environment-docker/tsconfig.json +16 -0
  75. package/packages/adapters-environment-docker/tsup.config.ts +9 -0
  76. package/packages/adapters-eventbus-cache/README.md +242 -0
  77. package/packages/adapters-eventbus-cache/eslint.config.js +27 -0
  78. package/packages/adapters-eventbus-cache/package.json +46 -0
  79. package/packages/adapters-eventbus-cache/src/index.test.ts +235 -0
  80. package/packages/adapters-eventbus-cache/src/index.ts +215 -0
  81. package/packages/adapters-eventbus-cache/src/manifest.ts +50 -0
  82. package/packages/adapters-eventbus-cache/src/types.ts +58 -0
  83. package/packages/adapters-eventbus-cache/tsconfig.build.json +15 -0
  84. package/packages/adapters-eventbus-cache/tsconfig.json +9 -0
  85. package/packages/adapters-eventbus-cache/tsup.config.ts +9 -0
  86. package/packages/adapters-fs/README.md +171 -0
  87. package/packages/adapters-fs/allowed.txt +1 -0
  88. package/packages/adapters-fs/conflict.txt +1 -0
  89. package/packages/adapters-fs/dest.txt +1 -0
  90. package/packages/adapters-fs/eslint.config.js +27 -0
  91. package/packages/adapters-fs/exists.txt +1 -0
  92. package/packages/adapters-fs/not-allowed.txt +1 -0
  93. package/packages/adapters-fs/other.txt +1 -0
  94. package/packages/adapters-fs/package.json +55 -0
  95. package/packages/adapters-fs/public/file1.txt +1 -0
  96. package/packages/adapters-fs/public/file2.txt +1 -0
  97. package/packages/adapters-fs/secret.txt +1 -0
  98. package/packages/adapters-fs/secrets/key.txt +1 -0
  99. package/packages/adapters-fs/src/index.test.ts +243 -0
  100. package/packages/adapters-fs/src/index.ts +258 -0
  101. package/packages/adapters-fs/src/manifest.ts +35 -0
  102. package/packages/adapters-fs/src/secure-storage.test.ts +380 -0
  103. package/packages/adapters-fs/src/secure-storage.ts +268 -0
  104. package/packages/adapters-fs/test.json +1 -0
  105. package/packages/adapters-fs/test.txt +1 -0
  106. package/packages/adapters-fs/test.xyz +1 -0
  107. package/packages/adapters-fs/test1.txt +1 -0
  108. package/packages/adapters-fs/test2.txt +1 -0
  109. package/packages/adapters-fs/tsconfig.build.json +15 -0
  110. package/packages/adapters-fs/tsconfig.json +9 -0
  111. package/packages/adapters-fs/tsup.config.ts +8 -0
  112. package/packages/adapters-fs/vitest.config.ts +19 -0
  113. package/packages/adapters-log-ringbuffer/README.md +228 -0
  114. package/packages/adapters-log-ringbuffer/eslint.config.js +27 -0
  115. package/packages/adapters-log-ringbuffer/package.json +47 -0
  116. package/packages/adapters-log-ringbuffer/src/__tests__/ring-buffer.test.ts +450 -0
  117. package/packages/adapters-log-ringbuffer/src/index.ts +212 -0
  118. package/packages/adapters-log-ringbuffer/src/manifest.ts +30 -0
  119. package/packages/adapters-log-ringbuffer/tsconfig.build.json +15 -0
  120. package/packages/adapters-log-ringbuffer/tsconfig.json +9 -0
  121. package/packages/adapters-log-ringbuffer/tsup.config.ts +9 -0
  122. package/packages/adapters-log-ringbuffer/vitest.config.ts +14 -0
  123. package/packages/adapters-log-sqlite/README.md +396 -0
  124. package/packages/adapters-log-sqlite/eslint.config.js +27 -0
  125. package/packages/adapters-log-sqlite/package.json +49 -0
  126. package/packages/adapters-log-sqlite/src/__tests__/log-persistence.test.ts +718 -0
  127. package/packages/adapters-log-sqlite/src/index.ts +1068 -0
  128. package/packages/adapters-log-sqlite/src/manifest.ts +36 -0
  129. package/packages/adapters-log-sqlite/src/schema.sql +46 -0
  130. package/packages/adapters-log-sqlite/tsconfig.build.json +15 -0
  131. package/packages/adapters-log-sqlite/tsconfig.json +9 -0
  132. package/packages/adapters-log-sqlite/tsup.config.ts +9 -0
  133. package/packages/adapters-log-sqlite/vitest.config.ts +15 -0
  134. package/packages/adapters-mongodb/README.md +147 -0
  135. package/packages/adapters-mongodb/eslint.config.js +27 -0
  136. package/packages/adapters-mongodb/package.json +53 -0
  137. package/packages/adapters-mongodb/src/index.ts +428 -0
  138. package/packages/adapters-mongodb/src/manifest.ts +45 -0
  139. package/packages/adapters-mongodb/src/secure-document.ts +231 -0
  140. package/packages/adapters-mongodb/tsconfig.build.json +15 -0
  141. package/packages/adapters-mongodb/tsconfig.json +9 -0
  142. package/packages/adapters-mongodb/tsup.config.ts +8 -0
  143. package/packages/adapters-openai/README.md +151 -0
  144. package/packages/adapters-openai/embeddings.ts +37 -0
  145. package/packages/adapters-openai/eslint.config.js +26 -0
  146. package/packages/adapters-openai/index.ts +22 -0
  147. package/packages/adapters-openai/package.json +57 -0
  148. package/packages/adapters-openai/src/embeddings-manifest.ts +45 -0
  149. package/packages/adapters-openai/src/embeddings.ts +104 -0
  150. package/packages/adapters-openai/src/index.ts +13 -0
  151. package/packages/adapters-openai/src/llm.ts +304 -0
  152. package/packages/adapters-openai/src/manifest.ts +47 -0
  153. package/packages/adapters-openai/tsconfig.build.json +15 -0
  154. package/packages/adapters-openai/tsconfig.json +9 -0
  155. package/packages/adapters-openai/tsup.config.ts +8 -0
  156. package/packages/adapters-pino/README.md +152 -0
  157. package/packages/adapters-pino/eslint.config.js +27 -0
  158. package/packages/adapters-pino/package.json +49 -0
  159. package/packages/adapters-pino/src/index.test.ts +44 -0
  160. package/packages/adapters-pino/src/index.ts +322 -0
  161. package/packages/adapters-pino/src/log-ring-buffer.ts +142 -0
  162. package/packages/adapters-pino/src/manifest.ts +49 -0
  163. package/packages/adapters-pino/tsconfig.build.json +15 -0
  164. package/packages/adapters-pino/tsconfig.json +9 -0
  165. package/packages/adapters-pino/tsup.config.ts +9 -0
  166. package/packages/adapters-pino-http/README.md +141 -0
  167. package/packages/adapters-pino-http/eslint.config.js +27 -0
  168. package/packages/adapters-pino-http/package.json +46 -0
  169. package/packages/adapters-pino-http/src/index.ts +229 -0
  170. package/packages/adapters-pino-http/tsconfig.build.json +15 -0
  171. package/packages/adapters-pino-http/tsconfig.json +9 -0
  172. package/packages/adapters-pino-http/tsup.config.ts +9 -0
  173. package/packages/adapters-qdrant/README.md +166 -0
  174. package/packages/adapters-qdrant/eslint.config.js +27 -0
  175. package/packages/adapters-qdrant/package.json +49 -0
  176. package/packages/adapters-qdrant/src/index.ts +490 -0
  177. package/packages/adapters-qdrant/src/manifest.ts +54 -0
  178. package/packages/adapters-qdrant/src/retry.ts +204 -0
  179. package/packages/adapters-qdrant/tsconfig.build.json +15 -0
  180. package/packages/adapters-qdrant/tsconfig.json +9 -0
  181. package/packages/adapters-qdrant/tsup.config.ts +9 -0
  182. package/packages/adapters-redis/README.md +159 -0
  183. package/packages/adapters-redis/eslint.config.js +27 -0
  184. package/packages/adapters-redis/package.json +49 -0
  185. package/packages/adapters-redis/src/index.ts +164 -0
  186. package/packages/adapters-redis/src/manifest.ts +49 -0
  187. package/packages/adapters-redis/tsconfig.build.json +15 -0
  188. package/packages/adapters-redis/tsconfig.json +9 -0
  189. package/packages/adapters-redis/tsup.config.ts +9 -0
  190. package/packages/adapters-snapshot-localfs/README.md +10 -0
  191. package/packages/adapters-snapshot-localfs/eslint.config.js +2 -0
  192. package/packages/adapters-snapshot-localfs/package.json +46 -0
  193. package/packages/adapters-snapshot-localfs/src/index.test.ts +40 -0
  194. package/packages/adapters-snapshot-localfs/src/index.ts +292 -0
  195. package/packages/adapters-snapshot-localfs/src/manifest.ts +32 -0
  196. package/packages/adapters-snapshot-localfs/tsconfig.build.json +15 -0
  197. package/packages/adapters-snapshot-localfs/tsconfig.json +16 -0
  198. package/packages/adapters-snapshot-localfs/tsup.config.ts +11 -0
  199. package/packages/adapters-sqlite/README.md +163 -0
  200. package/packages/adapters-sqlite/eslint.config.js +27 -0
  201. package/packages/adapters-sqlite/package.json +54 -0
  202. package/packages/adapters-sqlite/src/index.test.ts +245 -0
  203. package/packages/adapters-sqlite/src/index.ts +382 -0
  204. package/packages/adapters-sqlite/src/manifest.ts +47 -0
  205. package/packages/adapters-sqlite/src/secure-sql.test.ts +290 -0
  206. package/packages/adapters-sqlite/src/secure-sql.ts +281 -0
  207. package/packages/adapters-sqlite/tsconfig.build.json +15 -0
  208. package/packages/adapters-sqlite/tsconfig.json +9 -0
  209. package/packages/adapters-sqlite/tsup.config.ts +8 -0
  210. package/packages/adapters-sqlite/vitest.config.ts +19 -0
  211. package/packages/adapters-transport/README.md +170 -0
  212. package/packages/adapters-transport/eslint.config.js +27 -0
  213. package/packages/adapters-transport/package.json +49 -0
  214. package/packages/adapters-transport/src/__tests__/unix-socket-server.test.ts +550 -0
  215. package/packages/adapters-transport/src/index.ts +101 -0
  216. package/packages/adapters-transport/src/ipc-transport.ts +228 -0
  217. package/packages/adapters-transport/src/transport.ts +224 -0
  218. package/packages/adapters-transport/src/types.ts +92 -0
  219. package/packages/adapters-transport/src/unix-socket-server.ts +193 -0
  220. package/packages/adapters-transport/src/unix-socket-transport.ts +280 -0
  221. package/packages/adapters-transport/tsconfig.build.json +15 -0
  222. package/packages/adapters-transport/tsconfig.json +9 -0
  223. package/packages/adapters-transport/tsup.config.ts +9 -0
  224. package/packages/adapters-vibeproxy/README.md +159 -0
  225. package/packages/adapters-vibeproxy/eslint.config.js +27 -0
  226. package/packages/adapters-vibeproxy/package.json +51 -0
  227. package/packages/adapters-vibeproxy/src/index.ts +13 -0
  228. package/packages/adapters-vibeproxy/src/llm.ts +437 -0
  229. package/packages/adapters-vibeproxy/src/manifest.ts +51 -0
  230. package/packages/adapters-vibeproxy/tsconfig.build.json +15 -0
  231. package/packages/adapters-vibeproxy/tsconfig.json +9 -0
  232. package/packages/adapters-vibeproxy/tsup.config.ts +8 -0
  233. package/packages/adapters-workspace-agent/package.json +46 -0
  234. package/packages/adapters-workspace-agent/src/__tests__/adapter.test.ts +212 -0
  235. package/packages/adapters-workspace-agent/src/index.ts +220 -0
  236. package/packages/adapters-workspace-agent/src/manifest.ts +36 -0
  237. package/packages/adapters-workspace-agent/tsconfig.build.json +15 -0
  238. package/packages/adapters-workspace-agent/tsconfig.json +16 -0
  239. package/packages/adapters-workspace-agent/tsup.config.ts +11 -0
  240. package/packages/adapters-workspace-localfs/README.md +9 -0
  241. package/packages/adapters-workspace-localfs/eslint.config.js +2 -0
  242. package/packages/adapters-workspace-localfs/package.json +46 -0
  243. package/packages/adapters-workspace-localfs/src/index.test.ts +27 -0
  244. package/packages/adapters-workspace-localfs/src/index.ts +172 -0
  245. package/packages/adapters-workspace-localfs/src/manifest.ts +32 -0
  246. package/packages/adapters-workspace-localfs/tsconfig.build.json +15 -0
  247. package/packages/adapters-workspace-localfs/tsconfig.json +16 -0
  248. package/packages/adapters-workspace-localfs/tsup.config.ts +11 -0
  249. package/packages/adapters-workspace-worktree/README.md +9 -0
  250. package/packages/adapters-workspace-worktree/eslint.config.js +2 -0
  251. package/packages/adapters-workspace-worktree/package.json +46 -0
  252. package/packages/adapters-workspace-worktree/src/index.test.ts +38 -0
  253. package/packages/adapters-workspace-worktree/src/index.ts +245 -0
  254. package/packages/adapters-workspace-worktree/src/manifest.ts +38 -0
  255. package/packages/adapters-workspace-worktree/tsconfig.build.json +15 -0
  256. package/packages/adapters-workspace-worktree/tsconfig.json +16 -0
  257. package/packages/adapters-workspace-worktree/tsup.config.ts +11 -0
  258. package/pnpm-workspace.yaml +2800 -0
  259. package/prettierrc.json +1 -0
  260. package/scripts/devkit-sync.mjs +37 -0
  261. package/scripts/hooks/post-push +9 -0
  262. package/scripts/hooks/pre-commit +9 -0
  263. package/scripts/hooks/pre-push +9 -0
  264. package/test-integration.ts +242 -0
  265. package/test.txt +1 -0
  266. package/tsconfig.base.json +6 -0
  267. package/tsconfig.build.json +15 -0
  268. package/tsconfig.json +9 -0
  269. package/tsconfig.paths.json +26 -0
  270. package/tsconfig.tools.json +17 -0
  271. package/tsup.config.bin.ts +34 -0
  272. package/tsup.config.cli.ts +41 -0
  273. package/tsup.config.dual.ts +46 -0
  274. package/tsup.config.ts +36 -0
  275. package/tsup.external.json +103 -0
  276. package/vitest.config.ts +2 -0
@@ -0,0 +1,212 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { mkdtemp, readFile, rm } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { AgentWorkspaceAdapter } from '../index.js';
6
+ import type { MaterializeWorkspaceRequest } from '@kb-labs/core-platform';
7
+
8
+ // ── fetch mock helpers ────────────────────────────────────────────────────────
9
+
10
+ function mockFetchOk(result: unknown): void {
11
+ vi.stubGlobal('fetch', vi.fn(async () => ({
12
+ ok: true,
13
+ json: async () => ({ result }),
14
+ text: async () => JSON.stringify({ result }),
15
+ })));
16
+ }
17
+
18
+ function mockFetchError(status: number, body: string): void {
19
+ vi.stubGlobal('fetch', vi.fn(async () => ({
20
+ ok: false,
21
+ status,
22
+ text: async () => body,
23
+ })));
24
+ }
25
+
26
+ // ── tests ─────────────────────────────────────────────────────────────────────
27
+
28
+ describe('AgentWorkspaceAdapter', () => {
29
+ let cacheDir: string;
30
+
31
+ beforeEach(async () => {
32
+ cacheDir = await mkdtemp(join(tmpdir(), 'ws-agent-test-'));
33
+ });
34
+
35
+ afterEach(async () => {
36
+ vi.unstubAllGlobals();
37
+ await rm(cacheDir, { recursive: true, force: true });
38
+ });
39
+
40
+ describe('materialize', () => {
41
+ it('calls /internal/dispatch with filesystem.fetchWorkspace', async () => {
42
+ const files = [{ path: 'index.ts', content: 'export {}' }];
43
+ mockFetchOk(files);
44
+
45
+ const adapter = new AgentWorkspaceAdapter({
46
+ gatewayUrl: 'http://localhost:4000',
47
+ internalSecret: 'test-secret',
48
+ namespaceId: 'ns-1',
49
+ cacheDir,
50
+ });
51
+
52
+ const req: MaterializeWorkspaceRequest = { workspaceId: 'ws-test', basePath: '/workspace' };
53
+ await adapter.materialize(req);
54
+
55
+ const fetchMock = vi.mocked(fetch);
56
+ expect(fetchMock).toHaveBeenCalledOnce();
57
+ const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit];
58
+ expect(url).toBe('http://localhost:4000/internal/dispatch');
59
+ expect((init.headers as Record<string, string>)['x-internal-secret']).toBe('test-secret');
60
+ const body = JSON.parse(init.body as string);
61
+ expect(body.adapter).toBe('filesystem');
62
+ expect(body.method).toBe('fetchWorkspace');
63
+ expect(body.namespaceId).toBe('ns-1');
64
+ });
65
+
66
+ it('writes fetched files to cacheDir/workspaceId/', async () => {
67
+ const files = [
68
+ { path: 'index.ts', content: 'export {}' },
69
+ { path: join('src', 'app.ts'), content: 'const x = 1;' },
70
+ ];
71
+ mockFetchOk(files);
72
+
73
+ const adapter = new AgentWorkspaceAdapter({ cacheDir, internalSecret: 'secret' });
74
+
75
+ const descriptor = await adapter.materialize({ workspaceId: 'ws-abc', basePath: '.' });
76
+
77
+ expect(descriptor.workspaceId).toBe('ws-abc');
78
+ expect(descriptor.status).toBe('ready');
79
+ expect(descriptor.rootPath).toBe(join(cacheDir, 'ws-abc'));
80
+ expect(descriptor.provider).toBe('workspace-agent');
81
+
82
+ const indexContent = await readFile(join(cacheDir, 'ws-abc', 'index.ts'), 'utf-8');
83
+ expect(indexContent).toBe('export {}');
84
+
85
+ const appContent = await readFile(join(cacheDir, 'ws-abc', 'src', 'app.ts'), 'utf-8');
86
+ expect(appContent).toBe('const x = 1;');
87
+ });
88
+
89
+ it('generates workspaceId when not provided', async () => {
90
+ mockFetchOk([]);
91
+
92
+ const adapter = new AgentWorkspaceAdapter({ cacheDir, internalSecret: 'secret' });
93
+ const descriptor = await adapter.materialize({ basePath: '.' });
94
+
95
+ expect(descriptor.workspaceId).toMatch(/^ws_/);
96
+ });
97
+
98
+ it('stores file count in descriptor metadata', async () => {
99
+ const files = [
100
+ { path: 'a.ts', content: 'x' },
101
+ { path: 'b.ts', content: 'y' },
102
+ { path: 'c.ts', content: 'z' },
103
+ ];
104
+ mockFetchOk(files);
105
+
106
+ const adapter = new AgentWorkspaceAdapter({ cacheDir, internalSecret: 'secret' });
107
+ const descriptor = await adapter.materialize({ workspaceId: 'ws-meta', basePath: '.' });
108
+
109
+ expect(descriptor.metadata?.fileCount).toBe(3);
110
+ });
111
+
112
+ it('throws when dispatch returns non-array', async () => {
113
+ mockFetchOk({ notAnArray: true });
114
+
115
+ const adapter = new AgentWorkspaceAdapter({ cacheDir, internalSecret: 'secret' });
116
+
117
+ await expect(adapter.materialize({ workspaceId: 'ws-bad', basePath: '.' }))
118
+ .rejects.toThrow('expected array of files');
119
+ });
120
+
121
+ it('throws when fetch returns non-ok status', async () => {
122
+ mockFetchError(503, '{"error":"No host connected"}');
123
+
124
+ const adapter = new AgentWorkspaceAdapter({ cacheDir, internalSecret: 'secret' });
125
+
126
+ await expect(adapter.materialize({ workspaceId: 'ws-err', basePath: '.' }))
127
+ .rejects.toThrow('dispatch failed (503)');
128
+ });
129
+
130
+ it('passes hostId when configured', async () => {
131
+ mockFetchOk([]);
132
+
133
+ const adapter = new AgentWorkspaceAdapter({
134
+ cacheDir,
135
+ internalSecret: 'secret',
136
+ namespaceId: 'ns-x',
137
+ hostId: 'host-specific',
138
+ });
139
+
140
+ await adapter.materialize({ workspaceId: 'ws-host', basePath: '.' });
141
+
142
+ const fetchMock = vi.mocked(fetch);
143
+ const body = JSON.parse((fetchMock.mock.calls[0]![1] as RequestInit).body as string);
144
+ expect(body.hostId).toBe('host-specific');
145
+ });
146
+
147
+ it('uses basePath for remotePath', async () => {
148
+ mockFetchOk([]);
149
+
150
+ const adapter = new AgentWorkspaceAdapter({ cacheDir, internalSecret: 'secret' });
151
+ await adapter.materialize({ workspaceId: 'ws-path', basePath: '/home/user/myproject' });
152
+
153
+ const fetchMock = vi.mocked(fetch);
154
+ const body = JSON.parse((fetchMock.mock.calls[0]![1] as RequestInit).body as string);
155
+ expect(body.args).toEqual(['/home/user/myproject']);
156
+ });
157
+ });
158
+
159
+ describe('attach / getStatus / release', () => {
160
+ it('attach returns mountPath', async () => {
161
+ mockFetchOk([]);
162
+ const adapter = new AgentWorkspaceAdapter({ cacheDir, internalSecret: 'secret' });
163
+ await adapter.materialize({ workspaceId: 'ws-1', basePath: '.' });
164
+
165
+ const att = await adapter.attach({ workspaceId: 'ws-1', environmentId: 'env-1' });
166
+ expect(att.workspaceId).toBe('ws-1');
167
+ expect(att.mountPath).toBe('/workspace');
168
+ });
169
+
170
+ it('getStatus returns ready after materialize', async () => {
171
+ mockFetchOk([]);
172
+ const adapter = new AgentWorkspaceAdapter({ cacheDir, internalSecret: 'secret' });
173
+ await adapter.materialize({ workspaceId: 'ws-2', basePath: '.' });
174
+
175
+ const status = await adapter.getStatus('ws-2');
176
+ expect(status.status).toBe('ready');
177
+ });
178
+
179
+ it('getStatus throws for unknown workspace', async () => {
180
+ const adapter = new AgentWorkspaceAdapter({ cacheDir, internalSecret: 'secret' });
181
+ await expect(adapter.getStatus('unknown')).rejects.toThrow('Workspace not found');
182
+ });
183
+
184
+ it('release transitions status to released', async () => {
185
+ mockFetchOk([]);
186
+ const adapter = new AgentWorkspaceAdapter({ cacheDir, internalSecret: 'secret' });
187
+ await adapter.materialize({ workspaceId: 'ws-3', basePath: '.' });
188
+ await adapter.release('ws-3');
189
+
190
+ const status = await adapter.getStatus('ws-3');
191
+ expect(status.status).toBe('released');
192
+ });
193
+ });
194
+
195
+ describe('getCapabilities', () => {
196
+ it('reports remote:true', () => {
197
+ const adapter = new AgentWorkspaceAdapter({ internalSecret: 'secret' });
198
+ const caps = adapter.getCapabilities();
199
+ expect(caps.custom?.remote).toBe(true);
200
+ expect(caps.supportsAttach).toBe(true);
201
+ });
202
+ });
203
+
204
+ describe('createAdapter factory', () => {
205
+ it('exports createAdapter default function', async () => {
206
+ const mod = await import('../index.js');
207
+ expect(typeof mod.createAdapter).toBe('function');
208
+ const adapter = mod.createAdapter({ internalSecret: 'test' });
209
+ expect(adapter).toBeInstanceOf(AgentWorkspaceAdapter);
210
+ });
211
+ });
212
+ });
@@ -0,0 +1,220 @@
1
+ /**
2
+ * @module @kb-labs/adapters-workspace-agent
3
+ *
4
+ * IWorkspaceProvider implementation that materializes workspaces by fetching
5
+ * files from a local machine via Host Agent connected to the Gateway.
6
+ *
7
+ * Usage in kb.config.json (cloud mode):
8
+ * "platform": {
9
+ * "adapters": {
10
+ * "workspace": "@kb-labs/adapters-workspace-agent"
11
+ * },
12
+ * "adapterOptions": {
13
+ * "workspace": {
14
+ * "gatewayUrl": "http://localhost:4000",
15
+ * "namespaceId": "default",
16
+ * "cacheDir": ".kb/runtime/workspaces"
17
+ * }
18
+ * }
19
+ * }
20
+ *
21
+ * The adapter calls filesystem.fetchWorkspace on the connected Host Agent
22
+ * via Gateway's /internal/dispatch endpoint, writes files to a local cache
23
+ * dir, and returns a WorkspaceDescriptor with rootPath pointing to that cache.
24
+ * From that point on, workflow workers and Docker environments use rootPath
25
+ * as a normal local directory — no knowledge of remote origin.
26
+ */
27
+
28
+ import { mkdir, writeFile } from 'node:fs/promises';
29
+ import { join, dirname } from 'node:path';
30
+ import { randomUUID } from 'node:crypto';
31
+ import type {
32
+ IWorkspaceProvider,
33
+ MaterializeWorkspaceRequest,
34
+ WorkspaceDescriptor,
35
+ AttachWorkspaceRequest,
36
+ WorkspaceAttachment,
37
+ WorkspaceStatusResult,
38
+ WorkspaceProviderCapabilities,
39
+ WorkspaceStatus,
40
+ } from '@kb-labs/core-platform';
41
+
42
+ export { manifest } from './manifest.js';
43
+
44
+ export interface AgentWorkspaceAdapterConfig {
45
+ /** Gateway base URL. Default: 'http://localhost:4000' */
46
+ gatewayUrl?: string;
47
+ /** Shared secret for /internal/dispatch. Read from GATEWAY_INTERNAL_SECRET env if omitted. */
48
+ internalSecret?: string;
49
+ /** Gateway namespace to find the host agent in. Default: 'default' */
50
+ namespaceId?: string;
51
+ /** Local directory where fetched workspaces are cached. Default: '.kb/runtime/workspaces' */
52
+ cacheDir?: string;
53
+ /** Specific hostId to use. If omitted, Gateway picks first connected host in namespace. */
54
+ hostId?: string;
55
+ }
56
+
57
+ interface WorkspaceRecord {
58
+ workspaceId: string;
59
+ rootPath: string;
60
+ status: WorkspaceStatus;
61
+ updatedAt: string;
62
+ }
63
+
64
+ export class AgentWorkspaceAdapter implements IWorkspaceProvider {
65
+ private readonly gatewayUrl: string;
66
+ private readonly internalSecret: string;
67
+ private readonly namespaceId: string;
68
+ private readonly cacheDir: string;
69
+ private readonly hostId?: string;
70
+ private readonly records = new Map<string, WorkspaceRecord>();
71
+
72
+ constructor(config: AgentWorkspaceAdapterConfig = {}) {
73
+ this.gatewayUrl = (config.gatewayUrl ?? 'http://localhost:4000').replace(/\/$/, '');
74
+ this.internalSecret = config.internalSecret ?? process.env['GATEWAY_INTERNAL_SECRET'] ?? '';
75
+ this.namespaceId = config.namespaceId ?? 'default';
76
+ this.cacheDir = config.cacheDir ?? '.kb/runtime/workspaces';
77
+ this.hostId = config.hostId;
78
+ }
79
+
80
+ async materialize(request: MaterializeWorkspaceRequest): Promise<WorkspaceDescriptor> {
81
+ const workspaceId = request.workspaceId ?? `ws_${randomUUID()}`;
82
+ const now = new Date().toISOString();
83
+
84
+ // Determine which path to fetch from the agent
85
+ const remotePath = request.basePath ?? request.sourceRef ?? '.';
86
+
87
+ // Call filesystem.fetchWorkspace via Gateway /internal/dispatch
88
+ const files = await this.dispatch('filesystem', 'fetchWorkspace', [remotePath]) as Array<{ path: string; content: string }>;
89
+
90
+ if (!Array.isArray(files)) {
91
+ throw new Error(`AgentWorkspaceAdapter: expected array of files from fetchWorkspace, got ${typeof files}`);
92
+ }
93
+
94
+ // Write files to local cache
95
+ const rootPath = join(this.cacheDir, workspaceId);
96
+ await mkdir(rootPath, { recursive: true });
97
+
98
+ for (const file of files) {
99
+ const dest = join(rootPath, file.path);
100
+ await mkdir(dirname(dest), { recursive: true });
101
+ await writeFile(dest, file.content, 'utf-8');
102
+ }
103
+
104
+ const descriptor: WorkspaceDescriptor = {
105
+ workspaceId,
106
+ provider: 'workspace-agent',
107
+ status: 'ready',
108
+ rootPath,
109
+ createdAt: now,
110
+ updatedAt: now,
111
+ metadata: {
112
+ sourceRef: request.sourceRef,
113
+ remotePath,
114
+ namespaceId: this.namespaceId,
115
+ fileCount: files.length,
116
+ },
117
+ };
118
+
119
+ this.records.set(workspaceId, {
120
+ workspaceId,
121
+ rootPath,
122
+ status: 'ready',
123
+ updatedAt: now,
124
+ });
125
+
126
+ return descriptor;
127
+ }
128
+
129
+ async attach(request: AttachWorkspaceRequest): Promise<WorkspaceAttachment> {
130
+ const now = new Date().toISOString();
131
+ const record = this.getRecordOrThrow(request.workspaceId);
132
+ record.status = 'attached';
133
+ record.updatedAt = now;
134
+
135
+ return {
136
+ workspaceId: request.workspaceId,
137
+ environmentId: request.environmentId,
138
+ mountPath: request.mountPath ?? '/workspace',
139
+ attachedAt: now,
140
+ metadata: {
141
+ rootPath: record.rootPath,
142
+ readOnly: request.readOnly ?? false,
143
+ },
144
+ };
145
+ }
146
+
147
+ async release(workspaceId: string): Promise<void> {
148
+ const record = this.records.get(workspaceId);
149
+ if (record) {
150
+ record.status = 'released';
151
+ record.updatedAt = new Date().toISOString();
152
+ }
153
+ // Cached files kept on disk — GC handled separately
154
+ }
155
+
156
+ async getStatus(workspaceId: string): Promise<WorkspaceStatusResult> {
157
+ const record = this.getRecordOrThrow(workspaceId);
158
+ return {
159
+ workspaceId,
160
+ status: record.status,
161
+ updatedAt: record.updatedAt,
162
+ };
163
+ }
164
+
165
+ getCapabilities(): WorkspaceProviderCapabilities {
166
+ return {
167
+ supportsAttach: true,
168
+ supportsRelease: true,
169
+ supportsReadOnlyMounts: true,
170
+ custom: {
171
+ provider: 'agent',
172
+ remote: true,
173
+ },
174
+ };
175
+ }
176
+
177
+ /**
178
+ * Call an adapter method on the connected Host Agent via Gateway /internal/dispatch.
179
+ */
180
+ private async dispatch(adapter: string, method: string, args: unknown[]): Promise<unknown> {
181
+ const url = `${this.gatewayUrl}/internal/dispatch`;
182
+
183
+ const response = await fetch(url, {
184
+ method: 'POST',
185
+ headers: {
186
+ 'Content-Type': 'application/json',
187
+ 'x-internal-secret': this.internalSecret,
188
+ },
189
+ body: JSON.stringify({
190
+ namespaceId: this.namespaceId,
191
+ hostId: this.hostId,
192
+ adapter,
193
+ method,
194
+ args,
195
+ }),
196
+ });
197
+
198
+ if (!response.ok) {
199
+ const text = await response.text().catch(() => '');
200
+ throw new Error(
201
+ `AgentWorkspaceAdapter: dispatch failed (${response.status}): ${text}`,
202
+ );
203
+ }
204
+
205
+ const json = await response.json() as { result: unknown };
206
+ return json.result;
207
+ }
208
+
209
+ private getRecordOrThrow(workspaceId: string): WorkspaceRecord {
210
+ const record = this.records.get(workspaceId);
211
+ if (!record) { throw new Error(`Workspace not found: ${workspaceId}`); }
212
+ return record;
213
+ }
214
+ }
215
+
216
+ export function createAdapter(config?: AgentWorkspaceAdapterConfig): AgentWorkspaceAdapter {
217
+ return new AgentWorkspaceAdapter(config);
218
+ }
219
+
220
+ export default createAdapter;
@@ -0,0 +1,36 @@
1
+ import type { AdapterManifest } from '@kb-labs/core-platform';
2
+
3
+ export const manifest: AdapterManifest = {
4
+ manifestVersion: '1.0.0',
5
+ id: 'agent-workspace-provider',
6
+ name: 'Agent Workspace Provider',
7
+ version: '0.1.0',
8
+ description: 'Workspace provider that fetches files from a local machine via Host Agent connected to the Gateway',
9
+ author: 'KB Labs Team',
10
+ license: 'KBPL-1.1',
11
+ type: 'core',
12
+ implements: 'IWorkspaceProvider',
13
+ contexts: ['workspace'],
14
+ capabilities: {
15
+ custom: {
16
+ provider: 'agent',
17
+ remote: true,
18
+ },
19
+ },
20
+ configSchema: {
21
+ namespaceId: {
22
+ type: 'string',
23
+ default: 'default',
24
+ description: 'Gateway namespace to find the connected Host Agent',
25
+ },
26
+ cacheDir: {
27
+ type: 'string',
28
+ default: '.kb/runtime/workspaces',
29
+ description: 'Local directory where fetched workspace files are cached',
30
+ },
31
+ hostId: {
32
+ type: 'string',
33
+ description: 'Specific Host Agent ID to use (optional — defaults to first connected host)',
34
+ },
35
+ },
36
+ };
@@ -0,0 +1,15 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "baseUrl": ".",
6
+ "paths": {}
7
+ },
8
+ "include": [
9
+ "src/**/*"
10
+ ],
11
+ "exclude": [
12
+ "dist",
13
+ "node_modules"
14
+ ]
15
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "extends": "@kb-labs/devkit/tsconfig/node.json",
4
+ "compilerOptions": {
5
+ "baseUrl": ".",
6
+ "paths": {
7
+ "@kb-labs/core-platform": [
8
+ "../../../kb-labs-core/packages/core-platform/src/index.ts"
9
+ ],
10
+ "@kb-labs/core-platform/*": [
11
+ "../../../kb-labs-core/packages/core-platform/src/*"
12
+ ]
13
+ }
14
+ },
15
+ "include": ["src"]
16
+ }
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig({
4
+ entry: ['src/index.ts', 'src/manifest.ts'],
5
+ format: ['esm'],
6
+ dts: true,
7
+ sourcemap: true,
8
+ clean: true,
9
+ target: 'es2022',
10
+ external: [/^@kb-labs\/.*/],
11
+ });
@@ -0,0 +1,9 @@
1
+ # @kb-labs/adapters-workspace-localfs
2
+
3
+ Local filesystem implementation of `IWorkspaceProvider`.
4
+
5
+ ## Features
6
+
7
+ - Materialize workspace from existing path or create dedicated directory
8
+ - Attach/release status for environment mount lifecycle
9
+ - Workspace registry persisted for integration with snapshot adapters
@@ -0,0 +1,2 @@
1
+ import cfg from '@kb-labs/devkit/eslint/node.js';
2
+ export default cfg;
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@kb-labs/adapters-workspace-localfs",
3
+ "version": "0.5.0",
4
+ "description": "Local filesystem adapter implementing IWorkspaceProvider",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md"
17
+ ],
18
+ "sideEffects": false,
19
+ "scripts": {
20
+ "clean": "rimraf dist",
21
+ "build": "tsup",
22
+ "dev": "tsup --watch",
23
+ "type-check": "tsc --noEmit",
24
+ "test": "vitest run --passWithNoTests",
25
+ "test:watch": "vitest",
26
+ "lint": "eslint src --ext .ts",
27
+ "lint:fix": "eslint . --fix"
28
+ },
29
+ "peerDependencies": {
30
+ "@kb-labs/core-platform": "*"
31
+ },
32
+ "devDependencies": {
33
+ "@kb-labs/core-platform": "link:../../../../platform/kb-labs-core/packages/core-platform",
34
+ "@types/node": "^24.3.3",
35
+ "eslint": "^9",
36
+ "tsup": "^8.5.0",
37
+ "typescript": "^5.6.3",
38
+ "vitest": "^3.2.4",
39
+ "@kb-labs/devkit": "link:../../../kb-labs-devkit",
40
+ "rimraf": "^6.0.1"
41
+ },
42
+ "engines": {
43
+ "node": ">=20.0.0",
44
+ "pnpm": ">=9.0.0"
45
+ }
46
+ }
@@ -0,0 +1,27 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { mkdtemp, rm } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import os from 'node:os';
5
+ import { LocalFsWorkspaceAdapter } from './index.js';
6
+
7
+ describe('LocalFsWorkspaceAdapter', () => {
8
+ it('materializes and attaches workspace', async () => {
9
+ const tmp = await mkdtemp(path.join(os.tmpdir(), 'kb-workspace-'));
10
+ try {
11
+ const adapter = new LocalFsWorkspaceAdapter({ workspace: { cwd: tmp } });
12
+ const workspace = await adapter.materialize({ basePath: './demo' });
13
+ expect(workspace.status).toBe('ready');
14
+
15
+ const attachment = await adapter.attach({
16
+ workspaceId: workspace.workspaceId,
17
+ environmentId: 'env_1',
18
+ });
19
+ expect(attachment.mountPath).toBe('/workspace');
20
+
21
+ const status = await adapter.getStatus(workspace.workspaceId);
22
+ expect(status.status).toBe('attached');
23
+ } finally {
24
+ await rm(tmp, { recursive: true, force: true });
25
+ }
26
+ });
27
+ });