@a5c-ai/krate 5.0.1-staging.04a3db697

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 (246) hide show
  1. package/Dockerfile +31 -0
  2. package/README.md +183 -0
  3. package/bin/krate-demo.mjs +23 -0
  4. package/bin/krate-server.mjs +14 -0
  5. package/dist/krate-controller-ui.json +3067 -0
  6. package/dist/krate-lifecycle.json +201 -0
  7. package/dist/krate-runtime-snapshot.json +2955 -0
  8. package/dist/krate-summary.json +722 -0
  9. package/docs/README.md +61 -0
  10. package/docs/agents/README.md +83 -0
  11. package/docs/agents/acceptance-test-matrix.md +193 -0
  12. package/docs/agents/agent-mux-adapter-contract.md +167 -0
  13. package/docs/agents/agent-mux-source-map.md +310 -0
  14. package/docs/agents/agent-run-memory-import-spec.md +256 -0
  15. package/docs/agents/agent-stack-management-spec.md +421 -0
  16. package/docs/agents/api-contract-spec.md +309 -0
  17. package/docs/agents/artifacts-writeback-spec.md +145 -0
  18. package/docs/agents/chart-packaging-spec.md +128 -0
  19. package/docs/agents/ci-orchestration-spec.md +140 -0
  20. package/docs/agents/context-assembly-spec.md +219 -0
  21. package/docs/agents/controller-reconciliation-spec.md +255 -0
  22. package/docs/agents/crd-schema-spec.md +315 -0
  23. package/docs/agents/decision-log-open-questions.md +169 -0
  24. package/docs/agents/developer-implementation-checklist.md +329 -0
  25. package/docs/agents/dispatching-design.md +262 -0
  26. package/docs/agents/gaps-agent-mux-to-krate-crds.md +298 -0
  27. package/docs/agents/glossary.md +66 -0
  28. package/docs/agents/implementation-blueprint.md +324 -0
  29. package/docs/agents/implementation-rollout-slices.md +251 -0
  30. package/docs/agents/memory-context-integration-spec.md +194 -0
  31. package/docs/agents/memory-ontology-schema-spec.md +253 -0
  32. package/docs/agents/memory-operations-runbook.md +121 -0
  33. package/docs/agents/mvp-vertical-slice-spec.md +146 -0
  34. package/docs/agents/observability-audit-spec.md +265 -0
  35. package/docs/agents/operator-runbook.md +174 -0
  36. package/docs/agents/org-memory-api-payload-examples.md +333 -0
  37. package/docs/agents/org-memory-controller-sequence-spec.md +181 -0
  38. package/docs/agents/org-memory-e2e-fixture-plan.md +161 -0
  39. package/docs/agents/org-memory-ui-implementation-map.md +114 -0
  40. package/docs/agents/org-memory-vertical-slice-spec.md +168 -0
  41. package/docs/agents/org-resource-model-delta-spec.md +111 -0
  42. package/docs/agents/org-route-resource-model-spec.md +183 -0
  43. package/docs/agents/org-scoping-namespace-spec.md +114 -0
  44. package/docs/agents/rbac-secrets-management-spec.md +406 -0
  45. package/docs/agents/repository-page-integration-spec.md +255 -0
  46. package/docs/agents/resource-contract-examples.md +808 -0
  47. package/docs/agents/resource-relationship-map.md +190 -0
  48. package/docs/agents/security-threat-model.md +188 -0
  49. package/docs/agents/shared-memory-company-brain-spec.md +358 -0
  50. package/docs/agents/storage-migration-spec.md +168 -0
  51. package/docs/agents/subagent-orchestration-spec.md +152 -0
  52. package/docs/agents/system-overview.md +88 -0
  53. package/docs/agents/tools-mcp-skills-spec.md +189 -0
  54. package/docs/agents/traceability-matrix.md +79 -0
  55. package/docs/agents/ui-flow-spec.md +211 -0
  56. package/docs/agents/ui-ux-system-spec.md +426 -0
  57. package/docs/agents/workspace-lifecycle-spec.md +166 -0
  58. package/docs/architecture-spec.md +78 -0
  59. package/docs/components/control-plane.md +78 -0
  60. package/docs/components/data-plane.md +69 -0
  61. package/docs/components/hooks-events.md +67 -0
  62. package/docs/components/identity-rbac-policy.md +73 -0
  63. package/docs/components/kubevela-oam.md +70 -0
  64. package/docs/components/operations-publishing.md +81 -0
  65. package/docs/components/runners-ci.md +66 -0
  66. package/docs/components/web-ui.md +94 -0
  67. package/docs/external/README.md +47 -0
  68. package/docs/external/bidirectional-sync-design.md +134 -0
  69. package/docs/external/cicd-interface.md +64 -0
  70. package/docs/external/external-backend-controllers.md +170 -0
  71. package/docs/external/external-backend-crds.md +234 -0
  72. package/docs/external/external-backend-ui-spec.md +151 -0
  73. package/docs/external/external-backend-ux-flows.md +115 -0
  74. package/docs/external/external-object-mapping.md +125 -0
  75. package/docs/external/git-forge-interface.md +68 -0
  76. package/docs/external/github-integration-design.md +151 -0
  77. package/docs/external/issue-tracking-interface.md +66 -0
  78. package/docs/external/provider-capability-manifests.md +204 -0
  79. package/docs/external/provider-catalog.md +139 -0
  80. package/docs/external/provider-rollout-testing.md +78 -0
  81. package/docs/external/research-results.md +48 -0
  82. package/docs/external/security-auth-permissions.md +81 -0
  83. package/docs/external/sync-state-machines.md +108 -0
  84. package/docs/external/unified-external-backend-model.md +107 -0
  85. package/docs/external/user-facing-changes.md +67 -0
  86. package/docs/gaps.md +161 -0
  87. package/docs/install.md +94 -0
  88. package/docs/krate-design.md +334 -0
  89. package/docs/local-minikube.md +55 -0
  90. package/docs/ontology/README.md +32 -0
  91. package/docs/ontology/bounded-contexts.md +29 -0
  92. package/docs/ontology/events-and-hooks.md +32 -0
  93. package/docs/ontology/oam-kubevela.md +32 -0
  94. package/docs/ontology/operations-and-release.md +25 -0
  95. package/docs/ontology/personas-and-actors.md +32 -0
  96. package/docs/ontology/policies-and-invariants.md +33 -0
  97. package/docs/ontology/problem-space.md +30 -0
  98. package/docs/ontology/resource-contracts.md +40 -0
  99. package/docs/ontology/resource-taxonomy.md +42 -0
  100. package/docs/ontology/runners-and-ci.md +29 -0
  101. package/docs/ontology/solution-space.md +24 -0
  102. package/docs/ontology/storage-and-data-boundaries.md +29 -0
  103. package/docs/ontology/validation-matrix.md +24 -0
  104. package/docs/ontology/web-ui-excellent-flows.md +32 -0
  105. package/docs/ontology/workflows.md +39 -0
  106. package/docs/ontology/world.md +35 -0
  107. package/docs/product-requirements.md +62 -0
  108. package/docs/roadmap-mvp.md +87 -0
  109. package/docs/system-requirements.md +90 -0
  110. package/docs/tests/README.md +53 -0
  111. package/docs/tests/agent-qa-plan.md +63 -0
  112. package/docs/tests/browser-ui-tests.md +62 -0
  113. package/docs/tests/ci-quality-gates.md +48 -0
  114. package/docs/tests/coverage-model.md +64 -0
  115. package/docs/tests/e2e-scenario-tests.md +53 -0
  116. package/docs/tests/fixtures-test-data.md +63 -0
  117. package/docs/tests/observability-reliability-tests.md +54 -0
  118. package/docs/tests/product-test-matrix.md +145 -0
  119. package/docs/tests/qa-adoption-roadmap.md +130 -0
  120. package/docs/tests/qa-automation-plan.md +101 -0
  121. package/docs/tests/security-compliance-tests.md +57 -0
  122. package/docs/tests/test-framework-tools.md +88 -0
  123. package/docs/tests/test-suite-layout.md +121 -0
  124. package/docs/tests/unit-integration-tests.md +48 -0
  125. package/docs/todo-kyverno +714 -0
  126. package/docs/todos.md +4 -0
  127. package/docs/user-stories.md +78 -0
  128. package/examples/minikube-demo.yaml +190 -0
  129. package/examples/oam-application.yaml +23 -0
  130. package/examples/policy-kyverno-pr-title.yaml +18 -0
  131. package/package.json +63 -0
  132. package/scripts/build.mjs +29 -0
  133. package/scripts/setup-minikube.mjs +65 -0
  134. package/scripts/smoke.mjs +37 -0
  135. package/scripts/validate-doc-coverage.mjs +152 -0
  136. package/scripts/validate-package.mjs +93 -0
  137. package/scripts/validate-ui.mjs +236 -0
  138. package/src/agent-adapter-controller.js +169 -0
  139. package/src/agent-approval-controller.js +170 -0
  140. package/src/agent-context-bundles.js +242 -0
  141. package/src/agent-dispatch-controller.js +209 -0
  142. package/src/agent-gateway-config-controller.js +147 -0
  143. package/src/agent-memory-controller.js +357 -0
  144. package/src/agent-memory-import.js +327 -0
  145. package/src/agent-memory-query.js +292 -0
  146. package/src/agent-memory-repository-source-controller.js +255 -0
  147. package/src/agent-mux-client.js +280 -0
  148. package/src/agent-permission-review.js +250 -0
  149. package/src/agent-project-controller.js +117 -0
  150. package/src/agent-provider-config-controller.js +150 -0
  151. package/src/agent-secret-config-grant-controller.js +282 -0
  152. package/src/agent-session-transcript-controller.js +189 -0
  153. package/src/agent-stack-controller.js +347 -0
  154. package/src/agent-subagent-controller.js +160 -0
  155. package/src/agent-transport-binding-controller.js +121 -0
  156. package/src/agent-trigger-controller.js +321 -0
  157. package/src/agent-workspace-controller.js +447 -0
  158. package/src/agent-writeback-controller.js +302 -0
  159. package/src/api-controller.js +541 -0
  160. package/src/argocd-gitops.js +43 -0
  161. package/src/async-controller.js +207 -0
  162. package/src/audit-controller.js +191 -0
  163. package/src/auth.js +307 -0
  164. package/src/component-catalog.js +41 -0
  165. package/src/control-plane.js +136 -0
  166. package/src/controller-client.js +50 -0
  167. package/src/controller-ui.js +551 -0
  168. package/src/data-plane.js +178 -0
  169. package/src/event-bus.js +61 -0
  170. package/src/external/conflict-controller.js +225 -0
  171. package/src/external/github/auth.js +96 -0
  172. package/src/external/github/cicd.js +180 -0
  173. package/src/external/github/git-forge.js +240 -0
  174. package/src/external/github/index.js +144 -0
  175. package/src/external/github/issue-tracking.js +163 -0
  176. package/src/external/provider-adapter.js +161 -0
  177. package/src/external/provider-resource-factory.js +161 -0
  178. package/src/external/sync-controller.js +235 -0
  179. package/src/external/webhook-controller.js +144 -0
  180. package/src/external/write-controller.js +283 -0
  181. package/src/gitea-backend.js +95 -0
  182. package/src/gitea-service.js +173 -0
  183. package/src/handoff.js +98 -0
  184. package/src/hooks-events.js +63 -0
  185. package/src/http-server.js +377 -0
  186. package/src/identity-policy.js +86 -0
  187. package/src/index.js +55 -0
  188. package/src/kubernetes-controller-async.js +511 -0
  189. package/src/kubernetes-controller.js +878 -0
  190. package/src/kubernetes-resource-gateway.js +48 -0
  191. package/src/operations.js +112 -0
  192. package/src/org-scoping.js +5 -0
  193. package/src/resource-model.js +221 -0
  194. package/src/runners-ci.js +48 -0
  195. package/src/runtime.js +196 -0
  196. package/src/snapshot-cache.js +157 -0
  197. package/src/web-ui.js +40 -0
  198. package/tests/agent-adapter-controller.test.js +361 -0
  199. package/tests/agent-approval-controller.test.js +173 -0
  200. package/tests/agent-context-bundles.test.js +278 -0
  201. package/tests/agent-dispatch-controller.test.js +315 -0
  202. package/tests/agent-gateway-config-controller.test.js +386 -0
  203. package/tests/agent-memory-controller.test.js +308 -0
  204. package/tests/agent-memory-import-snapshot.test.js +477 -0
  205. package/tests/agent-memory-query.test.js +404 -0
  206. package/tests/agent-memory-repository-source.test.js +514 -0
  207. package/tests/agent-mux-client.test.js +204 -0
  208. package/tests/agent-permission-review-v2.test.js +317 -0
  209. package/tests/agent-permission-review.test.js +209 -0
  210. package/tests/agent-project-controller.test.js +302 -0
  211. package/tests/agent-provider-config-controller.test.js +376 -0
  212. package/tests/agent-resources.test.js +228 -0
  213. package/tests/agent-secret-config-grant.test.js +231 -0
  214. package/tests/agent-session-transcript-controller.test.js +499 -0
  215. package/tests/agent-stack-controller.test.js +221 -0
  216. package/tests/agent-subagent-controller.test.js +201 -0
  217. package/tests/agent-transport-binding-controller.test.js +294 -0
  218. package/tests/agent-trigger-controller.test.js +211 -0
  219. package/tests/agent-trigger-routes.test.js +190 -0
  220. package/tests/agent-trigger-sources.test.js +245 -0
  221. package/tests/agent-workspace-controller.test.js +181 -0
  222. package/tests/agent-writeback.test.js +292 -0
  223. package/tests/approval-persistence.test.js +171 -0
  224. package/tests/async-controller.test.js +252 -0
  225. package/tests/audit-controller.test.js +227 -0
  226. package/tests/deployment.test.js +396 -0
  227. package/tests/e2e/lifecycle.test.js +117 -0
  228. package/tests/external-github-forge.test.js +560 -0
  229. package/tests/external-github-issues-cicd.test.js +520 -0
  230. package/tests/external-integration.test.js +470 -0
  231. package/tests/external-persistence.test.js +340 -0
  232. package/tests/external-provider-adapter.test.js +365 -0
  233. package/tests/external-resource-model.test.js +215 -0
  234. package/tests/external-webhook-sync.test.js +287 -0
  235. package/tests/external-write-conflict.test.js +353 -0
  236. package/tests/gitea-service.test.js +253 -0
  237. package/tests/health-check-real.test.js +165 -0
  238. package/tests/integration/full-flow.test.js +266 -0
  239. package/tests/krate.test.js +727 -0
  240. package/tests/memory-search-wiring.test.js +270 -0
  241. package/tests/org-scoping.test.js +687 -0
  242. package/tests/session-cookie-hmac.test.js +151 -0
  243. package/tests/snapshot-performance.test.js +247 -0
  244. package/tests/sse-events.test.js +107 -0
  245. package/tests/workspace-volumes.test.js +312 -0
  246. package/tests/writeback-persistence.test.js +207 -0
@@ -0,0 +1,255 @@
1
+ // Agent Memory Repository & Source Controller — Slice 2.3a
2
+ //
3
+ // AgentMemoryRepository: org-level memory storage pointer, git repo ref validation.
4
+ // AgentMemorySource: read policies for memory paths, access control.
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // Boundaries
8
+ // ---------------------------------------------------------------------------
9
+
10
+ export const AGENT_MEMORY_REPOSITORY_CONTROLLER_BOUNDARY = {
11
+ role: 'agent-memory-repository-controller',
12
+ scope: 'AgentMemoryRepository lifecycle: validation, repo URL resolution, retention policy defaults',
13
+ owns: ['memory repository validation', 'repo URL', 'retention policy defaults'],
14
+ delegatesTo: ['resource-model'],
15
+ mustNotOwn: ['git operations', 'secret values', 'Agent Mux sessions', 'memory search']
16
+ };
17
+
18
+ export const AGENT_MEMORY_SOURCE_CONTROLLER_BOUNDARY = {
19
+ role: 'agent-memory-source-controller',
20
+ scope: 'AgentMemorySource lifecycle: validation, read policy, included/excluded paths, access control',
21
+ owns: ['memory source validation', 'read policy defaults', 'included paths', 'excluded paths'],
22
+ delegatesTo: ['resource-model'],
23
+ mustNotOwn: ['git operations', 'secret values', 'Agent Mux sessions', 'memory search']
24
+ };
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // Constants — defaults
28
+ // ---------------------------------------------------------------------------
29
+
30
+ const DEFAULT_RETENTION_POLICY = Object.freeze({
31
+ maxAgeDays: 90,
32
+ maxSizeMb: 500,
33
+ });
34
+
35
+ const DEFAULT_READ_POLICY = Object.freeze({
36
+ mode: 'allow-all',
37
+ maxDepth: 5,
38
+ });
39
+
40
+ // Valid git/http/ssh URL schemes
41
+ // Accepted forms:
42
+ // https://... http://... ssh://... git://... git@host:path (SCP-style)
43
+ const VALID_REPO_URL_PATTERNS = [
44
+ /^https?:\/\//,
45
+ /^ssh:\/\//,
46
+ /^git:\/\//,
47
+ /^git@[^:]+:.+/,
48
+ ];
49
+
50
+ // ---------------------------------------------------------------------------
51
+ // Standalone validateMemoryRepository
52
+ // ---------------------------------------------------------------------------
53
+
54
+ /**
55
+ * Validate an AgentMemoryRepository resource. Returns { valid, errors }.
56
+ * @param {object} resource
57
+ * @returns {{ valid: boolean, errors: string[] }}
58
+ */
59
+ export function validateMemoryRepository(resource) {
60
+ const errors = [];
61
+
62
+ // Guard against null/undefined
63
+ if (resource == null) {
64
+ errors.push('resource must not be null or undefined');
65
+ return { valid: false, errors };
66
+ }
67
+
68
+ // Validate metadata.name
69
+ if (!resource.metadata?.name) {
70
+ errors.push('metadata.name is required');
71
+ }
72
+
73
+ const spec = resource.spec || {};
74
+
75
+ // Validate organizationRef
76
+ if (!spec.organizationRef) {
77
+ errors.push('spec.organizationRef is required');
78
+ }
79
+
80
+ // Validate repoUrl — presence
81
+ if (!spec.repoUrl) {
82
+ errors.push('spec.repoUrl is required; provide a git/http/ssh repository URL');
83
+ } else {
84
+ // Validate repoUrl — format (git/http/ssh/scp-style)
85
+ const isValid = VALID_REPO_URL_PATTERNS.some((re) => re.test(spec.repoUrl));
86
+ if (!isValid) {
87
+ errors.push(
88
+ 'spec.repoUrl must be a valid git repository URL (https://, http://, ssh://, git://, or git@host:path format)'
89
+ );
90
+ }
91
+ }
92
+
93
+ return { valid: errors.length === 0, errors };
94
+ }
95
+
96
+ // ---------------------------------------------------------------------------
97
+ // AgentMemoryRepository controller factory
98
+ // ---------------------------------------------------------------------------
99
+
100
+ /**
101
+ * Factory that returns an AgentMemoryRepository controller instance.
102
+ */
103
+ export function createAgentMemoryRepositoryController() {
104
+ return {
105
+ role: 'agent-memory-repository-controller',
106
+
107
+ /**
108
+ * Validate an AgentMemoryRepository resource.
109
+ * @param {object} resource
110
+ * @returns {{ valid: boolean, errors: string[] }}
111
+ */
112
+ validateMemoryRepository(resource) {
113
+ return validateMemoryRepository(resource);
114
+ },
115
+
116
+ /**
117
+ * Return the repository URL configured in spec.repoUrl.
118
+ * @param {object} resource
119
+ * @returns {string}
120
+ */
121
+ getRepositoryUrl(resource) {
122
+ if (resource == null) {
123
+ throw new Error('resource must not be null or undefined');
124
+ }
125
+ return resource.spec?.repoUrl ?? null;
126
+ },
127
+
128
+ /**
129
+ * Return the effective retention policy for the memory repository.
130
+ * Merges spec.retentionPolicy with defaults; spec values take precedence.
131
+ * Defaults: maxAgeDays = 90, maxSizeMb = 500.
132
+ * @param {object} resource
133
+ * @returns {{ maxAgeDays: number, maxSizeMb: number }}
134
+ */
135
+ getRetentionPolicy(resource) {
136
+ if (resource == null) {
137
+ throw new Error('resource must not be null or undefined');
138
+ }
139
+ const specPolicy = resource.spec?.retentionPolicy ?? {};
140
+ return {
141
+ maxAgeDays: specPolicy.maxAgeDays ?? DEFAULT_RETENTION_POLICY.maxAgeDays,
142
+ maxSizeMb: specPolicy.maxSizeMb ?? DEFAULT_RETENTION_POLICY.maxSizeMb,
143
+ };
144
+ },
145
+ };
146
+ }
147
+
148
+ // ---------------------------------------------------------------------------
149
+ // Standalone validateMemorySource
150
+ // ---------------------------------------------------------------------------
151
+
152
+ /**
153
+ * Validate an AgentMemorySource resource. Returns { valid, errors }.
154
+ * @param {object} resource
155
+ * @returns {{ valid: boolean, errors: string[] }}
156
+ */
157
+ export function validateMemorySource(resource) {
158
+ const errors = [];
159
+
160
+ // Guard against null/undefined
161
+ if (resource == null) {
162
+ errors.push('resource must not be null or undefined');
163
+ return { valid: false, errors };
164
+ }
165
+
166
+ // Validate metadata.name
167
+ if (!resource.metadata?.name) {
168
+ errors.push('metadata.name is required');
169
+ }
170
+
171
+ const spec = resource.spec || {};
172
+
173
+ // Validate organizationRef
174
+ if (!spec.organizationRef) {
175
+ errors.push('spec.organizationRef is required');
176
+ }
177
+
178
+ // Validate repositoryRef
179
+ if (!spec.repositoryRef) {
180
+ errors.push('spec.repositoryRef is required; provide a reference to the AgentMemoryRepository');
181
+ }
182
+
183
+ // Validate paths — must be present and non-empty
184
+ const paths = spec.paths;
185
+ if (!Array.isArray(paths) || paths.length === 0) {
186
+ errors.push('spec.paths must be a non-empty array of memory path patterns');
187
+ }
188
+
189
+ return { valid: errors.length === 0, errors };
190
+ }
191
+
192
+ // ---------------------------------------------------------------------------
193
+ // AgentMemorySource controller factory
194
+ // ---------------------------------------------------------------------------
195
+
196
+ /**
197
+ * Factory that returns an AgentMemorySource controller instance.
198
+ */
199
+ export function createAgentMemorySourceController() {
200
+ return {
201
+ role: 'agent-memory-source-controller',
202
+
203
+ /**
204
+ * Validate an AgentMemorySource resource.
205
+ * @param {object} resource
206
+ * @returns {{ valid: boolean, errors: string[] }}
207
+ */
208
+ validateMemorySource(resource) {
209
+ return validateMemorySource(resource);
210
+ },
211
+
212
+ /**
213
+ * Return the effective read policy for the memory source.
214
+ * Merges spec.readPolicy with defaults; spec values take precedence.
215
+ * Defaults: mode = 'allow-all', maxDepth = 5.
216
+ * @param {object} resource
217
+ * @returns {{ mode: string, maxDepth: number }}
218
+ */
219
+ getReadPolicy(resource) {
220
+ if (resource == null) {
221
+ throw new Error('resource must not be null or undefined');
222
+ }
223
+ const specPolicy = resource.spec?.readPolicy ?? {};
224
+ return {
225
+ mode: specPolicy.mode ?? DEFAULT_READ_POLICY.mode,
226
+ maxDepth: specPolicy.maxDepth ?? DEFAULT_READ_POLICY.maxDepth,
227
+ };
228
+ },
229
+
230
+ /**
231
+ * Return the included memory path patterns from spec.paths.
232
+ * @param {object} resource
233
+ * @returns {string[]}
234
+ */
235
+ getIncludedPaths(resource) {
236
+ if (resource == null) {
237
+ throw new Error('resource must not be null or undefined');
238
+ }
239
+ return Array.isArray(resource.spec?.paths) ? [...resource.spec.paths] : [];
240
+ },
241
+
242
+ /**
243
+ * Return the excluded memory path patterns from spec.excludedPaths.
244
+ * Returns an empty array when not set.
245
+ * @param {object} resource
246
+ * @returns {string[]}
247
+ */
248
+ getExcludedPaths(resource) {
249
+ if (resource == null) {
250
+ throw new Error('resource must not be null or undefined');
251
+ }
252
+ return Array.isArray(resource.spec?.excludedPaths) ? [...resource.spec.excludedPaths] : [];
253
+ },
254
+ };
255
+ }
@@ -0,0 +1,280 @@
1
+ import http from 'node:http';
2
+ import https from 'node:https';
3
+ import { URL } from 'node:url';
4
+ import { createResource } from './resource-model.js';
5
+
6
+ export const AGENT_MUX_CLIENT_BOUNDARY = {
7
+ role: 'agent-mux-client',
8
+ scope: 'HTTP/SSE adapter for Agent Mux gateway — capabilities, sessions, events, transcripts',
9
+ owns: ['gateway HTTP calls', 'SSE event streaming', 'transcript reconciliation'],
10
+ delegatesTo: ['resource-model'],
11
+ mustNotOwn: ['secret values', 'permission review', 'resource persistence']
12
+ };
13
+
14
+ /**
15
+ * Internal HTTP request helper. Zero external deps — uses node:http / node:https.
16
+ * @param {string} url
17
+ * @param {{ method?: string, body?: object, headers?: Record<string,string>, timeout?: number }} options
18
+ * @returns {Promise<{ status: number, body: any }>}
19
+ */
20
+ function httpRequest(url, { method = 'GET', body, headers = {}, timeout = 30000 } = {}) {
21
+ return new Promise((resolve, reject) => {
22
+ const parsed = new URL(url);
23
+ const transport = parsed.protocol === 'https:' ? https : http;
24
+ const opts = {
25
+ hostname: parsed.hostname,
26
+ port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
27
+ path: parsed.pathname + parsed.search,
28
+ method,
29
+ headers: { 'Accept': 'application/json', ...headers },
30
+ timeout,
31
+ };
32
+ if (body) {
33
+ const payload = JSON.stringify(body);
34
+ opts.headers['Content-Type'] = 'application/json';
35
+ opts.headers['Content-Length'] = Buffer.byteLength(payload);
36
+ }
37
+ const req = transport.request(opts, (res) => {
38
+ const chunks = [];
39
+ res.on('data', chunk => chunks.push(chunk));
40
+ res.on('end', () => {
41
+ const raw = Buffer.concat(chunks).toString();
42
+ try {
43
+ resolve({ status: res.statusCode, body: JSON.parse(raw) });
44
+ } catch {
45
+ resolve({ status: res.statusCode, body: raw });
46
+ }
47
+ });
48
+ });
49
+ req.on('timeout', () => { req.destroy(); reject(new Error('Request timeout')); });
50
+ req.on('error', reject);
51
+ if (body) req.write(JSON.stringify(body));
52
+ req.end();
53
+ });
54
+ }
55
+
56
+ /**
57
+ * Parse SSE text into an array of parsed JSON data payloads.
58
+ * Each `data: {...}` line is extracted; malformed JSON is silently skipped.
59
+ * @param {string} text
60
+ * @returns {object[]}
61
+ */
62
+ export function parseSseLines(text) {
63
+ const events = [];
64
+ for (const block of text.split('\n\n')) {
65
+ for (const line of block.split('\n')) {
66
+ if (line.startsWith('data: ')) {
67
+ try { events.push(JSON.parse(line.slice(6))); } catch { /* skip malformed */ }
68
+ }
69
+ }
70
+ }
71
+ return events;
72
+ }
73
+
74
+ /**
75
+ * @param {{ gateway?: string, enabled?: boolean }} options
76
+ */
77
+ export function createAgentMuxClient(options = {}) {
78
+ const { gateway = '', enabled = false } = options;
79
+
80
+ return {
81
+ role: 'agent-mux-client',
82
+
83
+ isAvailable() {
84
+ return enabled && !!gateway;
85
+ },
86
+
87
+ /**
88
+ * Query adapter capabilities from the gateway.
89
+ * GET {gateway}/api/v1/agents/{adapter}/capabilities
90
+ * @param {string} adapter
91
+ * @returns {Promise<object|null>}
92
+ */
93
+ async queryCapabilities(adapter) {
94
+ if (!this.isAvailable()) return null;
95
+ try {
96
+ const { status, body } = await httpRequest(`${gateway}/api/v1/agents/${encodeURIComponent(adapter)}/capabilities`);
97
+ if (status >= 200 && status < 300) return body;
98
+ return null;
99
+ } catch {
100
+ return null;
101
+ }
102
+ },
103
+
104
+ /**
105
+ * Launch a new agent session through the gateway.
106
+ * POST {gateway}/api/v1/sessions
107
+ * @param {{ stack: object, contextBundle?: object, permissionSnapshot?: object, workspace?: object }} params
108
+ * @returns {Promise<{ runId: string, sessionId: string }|null>}
109
+ */
110
+ async launchSession({ stack, contextBundle, permissionSnapshot, workspace }) {
111
+ if (!this.isAvailable()) return null;
112
+ try {
113
+ const payload = {
114
+ agent: stack?.baseAgent,
115
+ model: stack?.model,
116
+ prompt: contextBundle?.prompt,
117
+ systemPrompt: contextBundle?.systemPrompt,
118
+ attachments: contextBundle?.attachments,
119
+ workspace: workspace?.mountPath || '/workspace',
120
+ };
121
+ const { status, body } = await httpRequest(`${gateway}/api/v1/sessions`, { method: 'POST', body: payload });
122
+ if (status >= 200 && status < 300 && body?.runId && body?.sessionId) {
123
+ return { runId: body.runId, sessionId: body.sessionId };
124
+ }
125
+ return null;
126
+ } catch {
127
+ return null;
128
+ }
129
+ },
130
+
131
+ /**
132
+ * Get session status from the gateway.
133
+ * GET {gateway}/api/v1/sessions/{sessionId}
134
+ * @param {string} sessionId
135
+ * @returns {Promise<object|null>}
136
+ */
137
+ async getSessionStatus(sessionId) {
138
+ if (!this.isAvailable()) return null;
139
+ try {
140
+ const { status, body } = await httpRequest(`${gateway}/api/v1/sessions/${encodeURIComponent(sessionId)}`);
141
+ if (status >= 200 && status < 300) return body;
142
+ return null;
143
+ } catch {
144
+ return null;
145
+ }
146
+ },
147
+
148
+ /**
149
+ * Subscribe to SSE events for a run. Reconnects with exponential backoff (1s, 2s, 4s... max 30s).
150
+ * GET {gateway}/api/v1/runs/{runId}/events (Accept: text/event-stream)
151
+ * @param {string} runId
152
+ * @param {(event: object) => void} callback
153
+ * @returns {{ abort: () => void }}
154
+ */
155
+ subscribeToEvents(runId, callback) {
156
+ let aborted = false;
157
+ let currentReq = null;
158
+ let backoff = 1000;
159
+
160
+ const connect = () => {
161
+ if (aborted) return;
162
+ try {
163
+ const parsed = new URL(`${gateway}/api/v1/runs/${encodeURIComponent(runId)}/events`);
164
+ const transport = parsed.protocol === 'https:' ? https : http;
165
+ const opts = {
166
+ hostname: parsed.hostname,
167
+ port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
168
+ path: parsed.pathname + parsed.search,
169
+ method: 'GET',
170
+ headers: { 'Accept': 'text/event-stream' },
171
+ };
172
+ currentReq = transport.request(opts, (res) => {
173
+ if (aborted) return;
174
+ // Reset backoff on successful connection
175
+ backoff = 1000;
176
+ let buffer = '';
177
+ res.on('data', (chunk) => {
178
+ if (aborted) return;
179
+ buffer += chunk.toString();
180
+ // Process complete SSE blocks (separated by double newlines)
181
+ const parts = buffer.split('\n\n');
182
+ // Keep the last part as it may be incomplete
183
+ buffer = parts.pop() || '';
184
+ for (const block of parts) {
185
+ for (const line of block.split('\n')) {
186
+ if (line.startsWith('data: ')) {
187
+ try {
188
+ callback(JSON.parse(line.slice(6)));
189
+ } catch { /* skip malformed */ }
190
+ }
191
+ }
192
+ }
193
+ });
194
+ res.on('end', () => {
195
+ if (!aborted) reconnect();
196
+ });
197
+ res.on('error', () => {
198
+ if (!aborted) reconnect();
199
+ });
200
+ });
201
+ currentReq.on('error', () => {
202
+ if (!aborted) reconnect();
203
+ });
204
+ currentReq.end();
205
+ } catch {
206
+ if (!aborted) reconnect();
207
+ }
208
+ };
209
+
210
+ const reconnect = () => {
211
+ if (aborted) return;
212
+ const delay = backoff;
213
+ backoff = Math.min(backoff * 2, 30000);
214
+ setTimeout(connect, delay);
215
+ };
216
+
217
+ connect();
218
+
219
+ return {
220
+ abort() {
221
+ aborted = true;
222
+ if (currentReq) {
223
+ currentReq.destroy();
224
+ currentReq = null;
225
+ }
226
+ }
227
+ };
228
+ },
229
+
230
+ /**
231
+ * Reconcile SSE events into an AgentSessionTranscript resource.
232
+ * Parses events by role, computes cost, creates the resource via createResource().
233
+ * @param {string} sessionId
234
+ * @param {object[]} events
235
+ * @param {{ namespace?: string, organizationRef?: string }} options
236
+ * @returns {object} AgentSessionTranscript resource
237
+ */
238
+ reconcileTranscript(sessionId, events, { namespace = 'default', organizationRef = 'default' } = {}) {
239
+ const messages = [];
240
+ let totalInputTokens = 0;
241
+ let totalOutputTokens = 0;
242
+
243
+ for (const event of events) {
244
+ if (!event || typeof event !== 'object') continue;
245
+ const role = event.role || 'unknown';
246
+ const content = event.content || event.text || event.message || '';
247
+ const node = {
248
+ role,
249
+ content: typeof content === 'string' ? content : JSON.stringify(content),
250
+ timestamp: event.timestamp || new Date().toISOString(),
251
+ };
252
+ if (event.toolUse) node.toolUse = event.toolUse;
253
+ if (event.toolResult) node.toolResult = event.toolResult;
254
+ messages.push(node);
255
+
256
+ // Accumulate token usage if present
257
+ if (event.usage) {
258
+ totalInputTokens += event.usage.inputTokens || 0;
259
+ totalOutputTokens += event.usage.outputTokens || 0;
260
+ }
261
+ }
262
+
263
+ return createResource(
264
+ 'AgentSessionTranscript',
265
+ { name: `transcript-${sessionId}`, namespace },
266
+ {
267
+ organizationRef,
268
+ sessionRef: sessionId,
269
+ messages,
270
+ cost: {
271
+ inputTokens: totalInputTokens,
272
+ outputTokens: totalOutputTokens,
273
+ totalTokens: totalInputTokens + totalOutputTokens,
274
+ },
275
+ },
276
+ { phase: 'Reconciled', reconciledAt: new Date().toISOString() }
277
+ );
278
+ },
279
+ };
280
+ }