@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.
- package/Dockerfile +31 -0
- package/README.md +183 -0
- package/bin/krate-demo.mjs +23 -0
- package/bin/krate-server.mjs +14 -0
- package/dist/krate-controller-ui.json +3067 -0
- package/dist/krate-lifecycle.json +201 -0
- package/dist/krate-runtime-snapshot.json +2955 -0
- package/dist/krate-summary.json +722 -0
- package/docs/README.md +61 -0
- package/docs/agents/README.md +83 -0
- package/docs/agents/acceptance-test-matrix.md +193 -0
- package/docs/agents/agent-mux-adapter-contract.md +167 -0
- package/docs/agents/agent-mux-source-map.md +310 -0
- package/docs/agents/agent-run-memory-import-spec.md +256 -0
- package/docs/agents/agent-stack-management-spec.md +421 -0
- package/docs/agents/api-contract-spec.md +309 -0
- package/docs/agents/artifacts-writeback-spec.md +145 -0
- package/docs/agents/chart-packaging-spec.md +128 -0
- package/docs/agents/ci-orchestration-spec.md +140 -0
- package/docs/agents/context-assembly-spec.md +219 -0
- package/docs/agents/controller-reconciliation-spec.md +255 -0
- package/docs/agents/crd-schema-spec.md +315 -0
- package/docs/agents/decision-log-open-questions.md +169 -0
- package/docs/agents/developer-implementation-checklist.md +329 -0
- package/docs/agents/dispatching-design.md +262 -0
- package/docs/agents/gaps-agent-mux-to-krate-crds.md +298 -0
- package/docs/agents/glossary.md +66 -0
- package/docs/agents/implementation-blueprint.md +324 -0
- package/docs/agents/implementation-rollout-slices.md +251 -0
- package/docs/agents/memory-context-integration-spec.md +194 -0
- package/docs/agents/memory-ontology-schema-spec.md +253 -0
- package/docs/agents/memory-operations-runbook.md +121 -0
- package/docs/agents/mvp-vertical-slice-spec.md +146 -0
- package/docs/agents/observability-audit-spec.md +265 -0
- package/docs/agents/operator-runbook.md +174 -0
- package/docs/agents/org-memory-api-payload-examples.md +333 -0
- package/docs/agents/org-memory-controller-sequence-spec.md +181 -0
- package/docs/agents/org-memory-e2e-fixture-plan.md +161 -0
- package/docs/agents/org-memory-ui-implementation-map.md +114 -0
- package/docs/agents/org-memory-vertical-slice-spec.md +168 -0
- package/docs/agents/org-resource-model-delta-spec.md +111 -0
- package/docs/agents/org-route-resource-model-spec.md +183 -0
- package/docs/agents/org-scoping-namespace-spec.md +114 -0
- package/docs/agents/rbac-secrets-management-spec.md +406 -0
- package/docs/agents/repository-page-integration-spec.md +255 -0
- package/docs/agents/resource-contract-examples.md +808 -0
- package/docs/agents/resource-relationship-map.md +190 -0
- package/docs/agents/security-threat-model.md +188 -0
- package/docs/agents/shared-memory-company-brain-spec.md +358 -0
- package/docs/agents/storage-migration-spec.md +168 -0
- package/docs/agents/subagent-orchestration-spec.md +152 -0
- package/docs/agents/system-overview.md +88 -0
- package/docs/agents/tools-mcp-skills-spec.md +189 -0
- package/docs/agents/traceability-matrix.md +79 -0
- package/docs/agents/ui-flow-spec.md +211 -0
- package/docs/agents/ui-ux-system-spec.md +426 -0
- package/docs/agents/workspace-lifecycle-spec.md +166 -0
- package/docs/architecture-spec.md +78 -0
- package/docs/components/control-plane.md +78 -0
- package/docs/components/data-plane.md +69 -0
- package/docs/components/hooks-events.md +67 -0
- package/docs/components/identity-rbac-policy.md +73 -0
- package/docs/components/kubevela-oam.md +70 -0
- package/docs/components/operations-publishing.md +81 -0
- package/docs/components/runners-ci.md +66 -0
- package/docs/components/web-ui.md +94 -0
- package/docs/external/README.md +47 -0
- package/docs/external/bidirectional-sync-design.md +134 -0
- package/docs/external/cicd-interface.md +64 -0
- package/docs/external/external-backend-controllers.md +170 -0
- package/docs/external/external-backend-crds.md +234 -0
- package/docs/external/external-backend-ui-spec.md +151 -0
- package/docs/external/external-backend-ux-flows.md +115 -0
- package/docs/external/external-object-mapping.md +125 -0
- package/docs/external/git-forge-interface.md +68 -0
- package/docs/external/github-integration-design.md +151 -0
- package/docs/external/issue-tracking-interface.md +66 -0
- package/docs/external/provider-capability-manifests.md +204 -0
- package/docs/external/provider-catalog.md +139 -0
- package/docs/external/provider-rollout-testing.md +78 -0
- package/docs/external/research-results.md +48 -0
- package/docs/external/security-auth-permissions.md +81 -0
- package/docs/external/sync-state-machines.md +108 -0
- package/docs/external/unified-external-backend-model.md +107 -0
- package/docs/external/user-facing-changes.md +67 -0
- package/docs/gaps.md +161 -0
- package/docs/install.md +94 -0
- package/docs/krate-design.md +334 -0
- package/docs/local-minikube.md +55 -0
- package/docs/ontology/README.md +32 -0
- package/docs/ontology/bounded-contexts.md +29 -0
- package/docs/ontology/events-and-hooks.md +32 -0
- package/docs/ontology/oam-kubevela.md +32 -0
- package/docs/ontology/operations-and-release.md +25 -0
- package/docs/ontology/personas-and-actors.md +32 -0
- package/docs/ontology/policies-and-invariants.md +33 -0
- package/docs/ontology/problem-space.md +30 -0
- package/docs/ontology/resource-contracts.md +40 -0
- package/docs/ontology/resource-taxonomy.md +42 -0
- package/docs/ontology/runners-and-ci.md +29 -0
- package/docs/ontology/solution-space.md +24 -0
- package/docs/ontology/storage-and-data-boundaries.md +29 -0
- package/docs/ontology/validation-matrix.md +24 -0
- package/docs/ontology/web-ui-excellent-flows.md +32 -0
- package/docs/ontology/workflows.md +39 -0
- package/docs/ontology/world.md +35 -0
- package/docs/product-requirements.md +62 -0
- package/docs/roadmap-mvp.md +87 -0
- package/docs/system-requirements.md +90 -0
- package/docs/tests/README.md +53 -0
- package/docs/tests/agent-qa-plan.md +63 -0
- package/docs/tests/browser-ui-tests.md +62 -0
- package/docs/tests/ci-quality-gates.md +48 -0
- package/docs/tests/coverage-model.md +64 -0
- package/docs/tests/e2e-scenario-tests.md +53 -0
- package/docs/tests/fixtures-test-data.md +63 -0
- package/docs/tests/observability-reliability-tests.md +54 -0
- package/docs/tests/product-test-matrix.md +145 -0
- package/docs/tests/qa-adoption-roadmap.md +130 -0
- package/docs/tests/qa-automation-plan.md +101 -0
- package/docs/tests/security-compliance-tests.md +57 -0
- package/docs/tests/test-framework-tools.md +88 -0
- package/docs/tests/test-suite-layout.md +121 -0
- package/docs/tests/unit-integration-tests.md +48 -0
- package/docs/todo-kyverno +714 -0
- package/docs/todos.md +4 -0
- package/docs/user-stories.md +78 -0
- package/examples/minikube-demo.yaml +190 -0
- package/examples/oam-application.yaml +23 -0
- package/examples/policy-kyverno-pr-title.yaml +18 -0
- package/package.json +63 -0
- package/scripts/build.mjs +29 -0
- package/scripts/setup-minikube.mjs +65 -0
- package/scripts/smoke.mjs +37 -0
- package/scripts/validate-doc-coverage.mjs +152 -0
- package/scripts/validate-package.mjs +93 -0
- package/scripts/validate-ui.mjs +236 -0
- package/src/agent-adapter-controller.js +169 -0
- package/src/agent-approval-controller.js +170 -0
- package/src/agent-context-bundles.js +242 -0
- package/src/agent-dispatch-controller.js +209 -0
- package/src/agent-gateway-config-controller.js +147 -0
- package/src/agent-memory-controller.js +357 -0
- package/src/agent-memory-import.js +327 -0
- package/src/agent-memory-query.js +292 -0
- package/src/agent-memory-repository-source-controller.js +255 -0
- package/src/agent-mux-client.js +280 -0
- package/src/agent-permission-review.js +250 -0
- package/src/agent-project-controller.js +117 -0
- package/src/agent-provider-config-controller.js +150 -0
- package/src/agent-secret-config-grant-controller.js +282 -0
- package/src/agent-session-transcript-controller.js +189 -0
- package/src/agent-stack-controller.js +347 -0
- package/src/agent-subagent-controller.js +160 -0
- package/src/agent-transport-binding-controller.js +121 -0
- package/src/agent-trigger-controller.js +321 -0
- package/src/agent-workspace-controller.js +447 -0
- package/src/agent-writeback-controller.js +302 -0
- package/src/api-controller.js +541 -0
- package/src/argocd-gitops.js +43 -0
- package/src/async-controller.js +207 -0
- package/src/audit-controller.js +191 -0
- package/src/auth.js +307 -0
- package/src/component-catalog.js +41 -0
- package/src/control-plane.js +136 -0
- package/src/controller-client.js +50 -0
- package/src/controller-ui.js +551 -0
- package/src/data-plane.js +178 -0
- package/src/event-bus.js +61 -0
- package/src/external/conflict-controller.js +225 -0
- package/src/external/github/auth.js +96 -0
- package/src/external/github/cicd.js +180 -0
- package/src/external/github/git-forge.js +240 -0
- package/src/external/github/index.js +144 -0
- package/src/external/github/issue-tracking.js +163 -0
- package/src/external/provider-adapter.js +161 -0
- package/src/external/provider-resource-factory.js +161 -0
- package/src/external/sync-controller.js +235 -0
- package/src/external/webhook-controller.js +144 -0
- package/src/external/write-controller.js +283 -0
- package/src/gitea-backend.js +95 -0
- package/src/gitea-service.js +173 -0
- package/src/handoff.js +98 -0
- package/src/hooks-events.js +63 -0
- package/src/http-server.js +377 -0
- package/src/identity-policy.js +86 -0
- package/src/index.js +55 -0
- package/src/kubernetes-controller-async.js +511 -0
- package/src/kubernetes-controller.js +878 -0
- package/src/kubernetes-resource-gateway.js +48 -0
- package/src/operations.js +112 -0
- package/src/org-scoping.js +5 -0
- package/src/resource-model.js +221 -0
- package/src/runners-ci.js +48 -0
- package/src/runtime.js +196 -0
- package/src/snapshot-cache.js +157 -0
- package/src/web-ui.js +40 -0
- package/tests/agent-adapter-controller.test.js +361 -0
- package/tests/agent-approval-controller.test.js +173 -0
- package/tests/agent-context-bundles.test.js +278 -0
- package/tests/agent-dispatch-controller.test.js +315 -0
- package/tests/agent-gateway-config-controller.test.js +386 -0
- package/tests/agent-memory-controller.test.js +308 -0
- package/tests/agent-memory-import-snapshot.test.js +477 -0
- package/tests/agent-memory-query.test.js +404 -0
- package/tests/agent-memory-repository-source.test.js +514 -0
- package/tests/agent-mux-client.test.js +204 -0
- package/tests/agent-permission-review-v2.test.js +317 -0
- package/tests/agent-permission-review.test.js +209 -0
- package/tests/agent-project-controller.test.js +302 -0
- package/tests/agent-provider-config-controller.test.js +376 -0
- package/tests/agent-resources.test.js +228 -0
- package/tests/agent-secret-config-grant.test.js +231 -0
- package/tests/agent-session-transcript-controller.test.js +499 -0
- package/tests/agent-stack-controller.test.js +221 -0
- package/tests/agent-subagent-controller.test.js +201 -0
- package/tests/agent-transport-binding-controller.test.js +294 -0
- package/tests/agent-trigger-controller.test.js +211 -0
- package/tests/agent-trigger-routes.test.js +190 -0
- package/tests/agent-trigger-sources.test.js +245 -0
- package/tests/agent-workspace-controller.test.js +181 -0
- package/tests/agent-writeback.test.js +292 -0
- package/tests/approval-persistence.test.js +171 -0
- package/tests/async-controller.test.js +252 -0
- package/tests/audit-controller.test.js +227 -0
- package/tests/deployment.test.js +396 -0
- package/tests/e2e/lifecycle.test.js +117 -0
- package/tests/external-github-forge.test.js +560 -0
- package/tests/external-github-issues-cicd.test.js +520 -0
- package/tests/external-integration.test.js +470 -0
- package/tests/external-persistence.test.js +340 -0
- package/tests/external-provider-adapter.test.js +365 -0
- package/tests/external-resource-model.test.js +215 -0
- package/tests/external-webhook-sync.test.js +287 -0
- package/tests/external-write-conflict.test.js +353 -0
- package/tests/gitea-service.test.js +253 -0
- package/tests/health-check-real.test.js +165 -0
- package/tests/integration/full-flow.test.js +266 -0
- package/tests/krate.test.js +727 -0
- package/tests/memory-search-wiring.test.js +270 -0
- package/tests/org-scoping.test.js +687 -0
- package/tests/session-cookie-hmac.test.js +151 -0
- package/tests/snapshot-performance.test.js +247 -0
- package/tests/sse-events.test.js +107 -0
- package/tests/workspace-volumes.test.js +312 -0
- package/tests/writeback-persistence.test.js +207 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// Agent Gateway Config Controller — Slice 1.2e
|
|
2
|
+
// Manages AgentGatewayConfig resources: gateway name, endpoint URL, feature flags,
|
|
3
|
+
// connection pool settings, and TLS configuration reference.
|
|
4
|
+
|
|
5
|
+
export const AGENT_GATEWAY_CONFIG_CONTROLLER_BOUNDARY = {
|
|
6
|
+
role: 'agent-gateway-config-controller',
|
|
7
|
+
scope: 'AgentGatewayConfig lifecycle: validation, endpoint resolution, feature flags, connection pool, TLS config ref',
|
|
8
|
+
owns: ['gateway config validation', 'endpoint URL', 'feature flags', 'connection pool defaults', 'TLS config ref'],
|
|
9
|
+
delegatesTo: ['resource-model'],
|
|
10
|
+
mustNotOwn: ['secret values', 'dispatch execution', 'Agent Mux sessions', 'adapter implementation']
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const DEFAULT_FEATURE_FLAGS = Object.freeze({
|
|
14
|
+
streaming: true,
|
|
15
|
+
reconnect: true,
|
|
16
|
+
healthCheck: true
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const DEFAULT_POOL_SETTINGS = Object.freeze({
|
|
20
|
+
maxConnections: 10,
|
|
21
|
+
timeoutMs: 30000
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Validate an AgentGatewayConfig resource. Returns { valid, errors }.
|
|
26
|
+
* @param {object} resource
|
|
27
|
+
* @returns {{ valid: boolean, errors: string[] }}
|
|
28
|
+
*/
|
|
29
|
+
export function validateAgentGatewayConfig(resource) {
|
|
30
|
+
const errors = [];
|
|
31
|
+
|
|
32
|
+
// Guard against null/undefined resource
|
|
33
|
+
if (resource == null) {
|
|
34
|
+
errors.push('resource must not be null or undefined');
|
|
35
|
+
return { valid: false, errors };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Validate metadata.name
|
|
39
|
+
if (!resource?.metadata?.name) {
|
|
40
|
+
errors.push('metadata.name is required');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const spec = resource?.spec || {};
|
|
44
|
+
|
|
45
|
+
// Validate organizationRef
|
|
46
|
+
if (!spec.organizationRef) {
|
|
47
|
+
errors.push('spec.organizationRef is required');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Validate endpoint URL
|
|
51
|
+
if (!spec.endpointUrl) {
|
|
52
|
+
errors.push('spec.endpointUrl is required; provide the Agent Mux gateway endpoint URL');
|
|
53
|
+
} else {
|
|
54
|
+
// Basic URL validation: must start with http:// or https:// or ws:// or wss://
|
|
55
|
+
const validProtocols = ['http://', 'https://', 'ws://', 'wss://'];
|
|
56
|
+
const hasValidProtocol = validProtocols.some((p) => spec.endpointUrl.startsWith(p));
|
|
57
|
+
if (!hasValidProtocol) {
|
|
58
|
+
errors.push(`spec.endpointUrl must start with one of: ${validProtocols.join(', ')}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Validate connectionPool if provided
|
|
63
|
+
const pool = spec.connectionPool;
|
|
64
|
+
if (pool != null) {
|
|
65
|
+
if (pool.maxConnections != null && (!Number.isInteger(pool.maxConnections) || pool.maxConnections < 1)) {
|
|
66
|
+
errors.push('spec.connectionPool.maxConnections must be a positive integer');
|
|
67
|
+
}
|
|
68
|
+
if (pool.timeoutMs != null && (!Number.isFinite(pool.timeoutMs) || pool.timeoutMs < 0)) {
|
|
69
|
+
errors.push('spec.connectionPool.timeoutMs must be a non-negative number');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return { valid: errors.length === 0, errors };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Factory that returns an AgentGatewayConfig controller instance.
|
|
78
|
+
*/
|
|
79
|
+
export function createAgentGatewayConfigController() {
|
|
80
|
+
return {
|
|
81
|
+
role: 'agent-gateway-config-controller',
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Validate an AgentGatewayConfig resource.
|
|
85
|
+
* @param {object} resource
|
|
86
|
+
* @returns {{ valid: boolean, errors: string[] }}
|
|
87
|
+
*/
|
|
88
|
+
validate(resource) {
|
|
89
|
+
return validateAgentGatewayConfig(resource);
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Return the effective endpoint URL for the gateway config.
|
|
94
|
+
* @param {object} resource
|
|
95
|
+
* @returns {string}
|
|
96
|
+
*/
|
|
97
|
+
getEndpointUrl(resource) {
|
|
98
|
+
if (resource == null) {
|
|
99
|
+
throw new Error('resource must not be null or undefined');
|
|
100
|
+
}
|
|
101
|
+
return resource?.spec?.endpointUrl ?? null;
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Return the effective feature flags for a gateway config.
|
|
106
|
+
* Merges spec.featureFlags with defaults; spec values take precedence.
|
|
107
|
+
* @param {object} resource
|
|
108
|
+
* @returns {{ streaming: boolean, reconnect: boolean, healthCheck: boolean, [key: string]: boolean }}
|
|
109
|
+
*/
|
|
110
|
+
getFeatureFlags(resource) {
|
|
111
|
+
if (resource == null) {
|
|
112
|
+
throw new Error('resource must not be null or undefined');
|
|
113
|
+
}
|
|
114
|
+
const specFlags = resource?.spec?.featureFlags ?? {};
|
|
115
|
+
return { ...DEFAULT_FEATURE_FLAGS, ...specFlags };
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Return the effective connection pool settings for a gateway config.
|
|
120
|
+
* Merges spec.connectionPool with defaults; spec values take precedence.
|
|
121
|
+
* @param {object} resource
|
|
122
|
+
* @returns {{ maxConnections: number, timeoutMs: number }}
|
|
123
|
+
*/
|
|
124
|
+
getConnectionPool(resource) {
|
|
125
|
+
if (resource == null) {
|
|
126
|
+
throw new Error('resource must not be null or undefined');
|
|
127
|
+
}
|
|
128
|
+
const specPool = resource?.spec?.connectionPool ?? {};
|
|
129
|
+
return {
|
|
130
|
+
maxConnections: specPool.maxConnections ?? DEFAULT_POOL_SETTINGS.maxConnections,
|
|
131
|
+
timeoutMs: specPool.timeoutMs ?? DEFAULT_POOL_SETTINGS.timeoutMs
|
|
132
|
+
};
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Return the TLS configuration reference from the spec, or null if not set.
|
|
137
|
+
* @param {object} resource
|
|
138
|
+
* @returns {string|null}
|
|
139
|
+
*/
|
|
140
|
+
getTlsConfigRef(resource) {
|
|
141
|
+
if (resource == null) {
|
|
142
|
+
throw new Error('resource must not be null or undefined');
|
|
143
|
+
}
|
|
144
|
+
return resource?.spec?.tlsConfigRef ?? null;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { createResource, clone } from './resource-model.js';
|
|
3
|
+
import { queryGraph, queryGrep, queryMemory } from './agent-memory-query.js';
|
|
4
|
+
|
|
5
|
+
export const AGENT_MEMORY_CONTROLLER_BOUNDARY = {
|
|
6
|
+
role: 'agent-memory-controller',
|
|
7
|
+
scope: 'Company Brain memory management — search, snapshots, redaction, imports, time-travel',
|
|
8
|
+
owns: ['memory search', 'snapshot pinning', 'redaction scanning', 'import lifecycle', 'ontology validation'],
|
|
9
|
+
delegatesTo: ['resource-model'],
|
|
10
|
+
mustNotOwn: ['git operations', 'secret values', 'Agent Mux sessions']
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// Redaction patterns (same as agent-context-bundles)
|
|
14
|
+
const REDACTION_PATTERNS = [
|
|
15
|
+
{ kind: 'secret-key', pattern: /(?:API_KEY|API_SECRET|SECRET_KEY|ACCESS_KEY|PRIVATE_KEY|AUTH_TOKEN|PASSWORD|PASSWD|CREDENTIALS?)\s*[=:]\s*['"]?([^\s'"}{,\]]+)/gi },
|
|
16
|
+
{ kind: 'provider-token', pattern: /\b(sk-[a-zA-Z0-9]{20,}|ghp_[a-zA-Z0-9]{36,}|gho_[a-zA-Z0-9]{36,}|glpat-[a-zA-Z0-9\-_]{20,}|xoxb-[a-zA-Z0-9\-]+|xoxp-[a-zA-Z0-9\-]+)\b/g },
|
|
17
|
+
{ kind: 'bearer-token', pattern: /Bearer\s+[a-zA-Z0-9\-._~+\/]+=*/gi },
|
|
18
|
+
{ kind: 'private-key', pattern: /-----BEGIN\s+(?:RSA\s+|EC\s+|DSA\s+|OPENSSH\s+)?PRIVATE\s+KEY-----[\s\S]*?-----END\s+(?:RSA\s+|EC\s+|DSA\s+|OPENSSH\s+)?PRIVATE\s+KEY-----/g },
|
|
19
|
+
{ kind: 'base64-credential', pattern: /\b[A-Za-z0-9+\/]{40,}={0,2}\b/g },
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
function sha256(data) {
|
|
23
|
+
return createHash('sha256').update(data).digest('hex');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const IMPORT_PHASES = ['Pending', 'Collecting', 'Redacting', 'Normalizing', 'Validating', 'AwaitingReview'];
|
|
27
|
+
|
|
28
|
+
export function createAgentMemoryController(options = {}) {
|
|
29
|
+
return {
|
|
30
|
+
role: 'agent-memory-controller',
|
|
31
|
+
|
|
32
|
+
createMemorySnapshot({ memoryRepository, requestedRef, resolvedCommit, queryManifest, selectedRecords, selectedDocuments, ontologyDigest, namespace = 'default', organizationRef = 'default' }) {
|
|
33
|
+
const queryManifestDigest = sha256(JSON.stringify(queryManifest || {}));
|
|
34
|
+
const selectedRecordsDigest = sha256(JSON.stringify(selectedRecords || []));
|
|
35
|
+
const selectedDocumentsDigest = sha256(JSON.stringify(selectedDocuments || []));
|
|
36
|
+
|
|
37
|
+
const now = new Date().toISOString();
|
|
38
|
+
const snapshotName = `memsnapshot-${sha256(memoryRepository + resolvedCommit + now).slice(0, 12)}`;
|
|
39
|
+
|
|
40
|
+
const snapshot = createResource('AgentMemorySnapshot', { name: snapshotName, namespace }, {
|
|
41
|
+
organizationRef,
|
|
42
|
+
memoryRepository,
|
|
43
|
+
requestedRef,
|
|
44
|
+
resolvedCommit,
|
|
45
|
+
queryManifestDigest,
|
|
46
|
+
selectedRecordsDigest,
|
|
47
|
+
selectedDocumentsDigest,
|
|
48
|
+
ontologyDigest: ontologyDigest || '',
|
|
49
|
+
recordCount: (selectedRecords || []).length,
|
|
50
|
+
documentCount: (selectedDocuments || []).length,
|
|
51
|
+
});
|
|
52
|
+
snapshot.status = { phase: 'Pinned', createdAt: now };
|
|
53
|
+
|
|
54
|
+
return snapshot;
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
queryMemory({ snapshotRef, requester, query, records = [], documents = [], namespace = 'default', organizationRef = 'default' }) {
|
|
58
|
+
const modes = query.modes || ['graph-and-grep'];
|
|
59
|
+
const results = { graph: null, grep: null };
|
|
60
|
+
|
|
61
|
+
if (modes.includes('graph-only') || modes.includes('graph-and-grep')) {
|
|
62
|
+
results.graph = this.searchGraph({
|
|
63
|
+
records,
|
|
64
|
+
kinds: query.graph?.kinds || [],
|
|
65
|
+
edgeDepth: query.graph?.edgeDepth ?? 2,
|
|
66
|
+
query: query.text || '',
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (modes.includes('grep-only') || modes.includes('graph-and-grep')) {
|
|
71
|
+
results.grep = this.searchGrep({
|
|
72
|
+
documents,
|
|
73
|
+
paths: query.grep?.paths || [],
|
|
74
|
+
pattern: query.text || '',
|
|
75
|
+
maxMatches: query.grep?.maxMatches ?? 25,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const now = new Date().toISOString();
|
|
80
|
+
const queryName = `memquery-${sha256(snapshotRef + requester + now).slice(0, 12)}`;
|
|
81
|
+
|
|
82
|
+
const queryResource = createResource('AgentMemoryQuery', { name: queryName, namespace }, {
|
|
83
|
+
organizationRef,
|
|
84
|
+
snapshotRef,
|
|
85
|
+
requester,
|
|
86
|
+
query: clone(query),
|
|
87
|
+
resultDigest: sha256(JSON.stringify(results)),
|
|
88
|
+
});
|
|
89
|
+
queryResource.status = { phase: 'Completed', executedAt: now };
|
|
90
|
+
|
|
91
|
+
return { queryResource, results };
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Execute a memory search using the real query engine (agent-memory-query.js).
|
|
96
|
+
*
|
|
97
|
+
* @param {object} params
|
|
98
|
+
* @param {string} params.query - Search text (required, non-empty).
|
|
99
|
+
* @param {string} [params.mode] - 'graph-only' | 'grep-only' | 'graph-and-grep' (default 'graph-and-grep').
|
|
100
|
+
* @param {Array} [params.records] - Graph records.
|
|
101
|
+
* @param {Array} [params.edges] - Flat edge list.
|
|
102
|
+
* @param {Array} [params.documents] - Grep documents.
|
|
103
|
+
* @param {object} [params.graphOptions] - Passed to queryGraph: { kinds, depth }.
|
|
104
|
+
* @param {object} [params.grepOptions] - Passed to queryGrep: { paths, context, maxMatches }.
|
|
105
|
+
* @returns {{ graph: object|null, grep: object|null, stats: object }}
|
|
106
|
+
*/
|
|
107
|
+
queryAgentMemory({ query, mode = 'graph-and-grep', records = [], edges = [], documents = [], graphOptions = {}, grepOptions = {} }) {
|
|
108
|
+
return queryMemory({ query, mode, records, edges, documents, graphOptions, grepOptions });
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
searchGraph({ records, edges = [], kinds = [], edgeDepth = 2, query = '' }) {
|
|
112
|
+
// Empty query: return all records matching kinds filter with score=1 (no text match)
|
|
113
|
+
if (!query || query.trim() === '') {
|
|
114
|
+
let candidates = records;
|
|
115
|
+
if (kinds.length > 0) {
|
|
116
|
+
candidates = candidates.filter(r => kinds.includes(r.nodeKind));
|
|
117
|
+
}
|
|
118
|
+
const matches = candidates.map(record => ({
|
|
119
|
+
record: clone(record),
|
|
120
|
+
score: 1,
|
|
121
|
+
edges: this._collectEdges(record, records, edgeDepth),
|
|
122
|
+
}));
|
|
123
|
+
return { matches, totalMatches: matches.length };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Non-empty query: delegate to the real query engine
|
|
127
|
+
return queryGraph({
|
|
128
|
+
records,
|
|
129
|
+
edges,
|
|
130
|
+
query,
|
|
131
|
+
kinds,
|
|
132
|
+
depth: edgeDepth,
|
|
133
|
+
});
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
_collectEdges(startRecord, allRecords, maxDepth) {
|
|
137
|
+
if (maxDepth <= 0 || !startRecord.edges || startRecord.edges.length === 0) return [];
|
|
138
|
+
|
|
139
|
+
const visited = new Set([startRecord.id]);
|
|
140
|
+
const collectedEdges = [];
|
|
141
|
+
let frontier = [startRecord];
|
|
142
|
+
|
|
143
|
+
for (let depth = 0; depth < maxDepth; depth++) {
|
|
144
|
+
const nextFrontier = [];
|
|
145
|
+
for (const record of frontier) {
|
|
146
|
+
for (const edge of (record.edges || [])) {
|
|
147
|
+
if (visited.has(edge.target)) continue;
|
|
148
|
+
visited.add(edge.target);
|
|
149
|
+
collectedEdges.push(clone(edge));
|
|
150
|
+
const targetRecord = allRecords.find(r => r.id === edge.target);
|
|
151
|
+
if (targetRecord) nextFrontier.push(targetRecord);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
frontier = nextFrontier;
|
|
155
|
+
if (frontier.length === 0) break;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return collectedEdges;
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
searchGrep({ documents, paths = [], pattern = '', maxMatches = 25 }) {
|
|
162
|
+
// Empty pattern: return empty (no-op, preserves backward-compatible behavior)
|
|
163
|
+
if (!pattern || pattern.trim() === '') {
|
|
164
|
+
return { excerpts: [], totalMatches: 0 };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Delegate to the real query engine, mapping pattern -> query
|
|
168
|
+
return queryGrep({
|
|
169
|
+
documents,
|
|
170
|
+
query: pattern,
|
|
171
|
+
paths,
|
|
172
|
+
maxMatches,
|
|
173
|
+
});
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
resolveTimeTravel({ mode = 'current', requestedRef, requestedTime, commits = [] }) {
|
|
177
|
+
const now = new Date().toISOString();
|
|
178
|
+
|
|
179
|
+
if (mode === 'current') {
|
|
180
|
+
const latest = commits[0] || null;
|
|
181
|
+
return {
|
|
182
|
+
resolvedCommit: latest?.sha || latest?.id || null,
|
|
183
|
+
resolvedAt: now,
|
|
184
|
+
mode: 'current',
|
|
185
|
+
staleBy: null,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (mode === 'explicit-ref') {
|
|
190
|
+
return {
|
|
191
|
+
resolvedCommit: requestedRef,
|
|
192
|
+
resolvedAt: now,
|
|
193
|
+
mode: 'explicit-ref',
|
|
194
|
+
staleBy: null,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (mode === 'ref-at-time') {
|
|
199
|
+
const targetTime = new Date(requestedTime).getTime();
|
|
200
|
+
let best = null;
|
|
201
|
+
for (const commit of commits) {
|
|
202
|
+
const commitTime = new Date(commit.timestamp || commit.date).getTime();
|
|
203
|
+
if (commitTime <= targetTime) {
|
|
204
|
+
if (!best || commitTime > new Date(best.timestamp || best.date).getTime()) {
|
|
205
|
+
best = commit;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const resolvedCommit = best?.sha || best?.id || null;
|
|
210
|
+
const staleBy = best ? (targetTime - new Date(best.timestamp || best.date).getTime()) : null;
|
|
211
|
+
return {
|
|
212
|
+
resolvedCommit,
|
|
213
|
+
resolvedAt: now,
|
|
214
|
+
mode: 'ref-at-time',
|
|
215
|
+
staleBy,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (mode === 'snapshot-tag') {
|
|
220
|
+
return {
|
|
221
|
+
resolvedCommit: requestedRef,
|
|
222
|
+
resolvedAt: now,
|
|
223
|
+
mode: 'snapshot-tag',
|
|
224
|
+
staleBy: null,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return { resolvedCommit: null, resolvedAt: now, mode, staleBy: null };
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
createImport({ organizationRef, memoryRepository, source, include, validationPolicy, namespace = 'default' }) {
|
|
232
|
+
const now = new Date().toISOString();
|
|
233
|
+
const importName = `memimport-${sha256(memoryRepository + source + now).slice(0, 12)}`;
|
|
234
|
+
|
|
235
|
+
const importResource = createResource('AgentRunMemoryImport', { name: importName, namespace }, {
|
|
236
|
+
organizationRef,
|
|
237
|
+
memoryRepository,
|
|
238
|
+
source,
|
|
239
|
+
include: clone(include),
|
|
240
|
+
validationPolicy: validationPolicy || 'none',
|
|
241
|
+
});
|
|
242
|
+
importResource.status = { phase: 'Pending', createdAt: now };
|
|
243
|
+
|
|
244
|
+
return importResource;
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
processImport({ importResource, content }) {
|
|
248
|
+
const updated = clone(importResource);
|
|
249
|
+
const currentPhase = updated.status?.phase || 'Pending';
|
|
250
|
+
const now = new Date().toISOString();
|
|
251
|
+
|
|
252
|
+
const currentIndex = IMPORT_PHASES.indexOf(currentPhase);
|
|
253
|
+
if (currentIndex < 0 || currentIndex >= IMPORT_PHASES.length - 1) {
|
|
254
|
+
return updated;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const nextPhase = IMPORT_PHASES[currentIndex + 1];
|
|
258
|
+
|
|
259
|
+
// Apply phase-specific logic
|
|
260
|
+
if (nextPhase === 'Redacting') {
|
|
261
|
+
const scan = this.scanForRedaction(content || '');
|
|
262
|
+
updated.status.redactionScan = {
|
|
263
|
+
clean: scan.clean,
|
|
264
|
+
redactionCount: scan.redactionCount,
|
|
265
|
+
redactionsByKind: clone(scan.redactionsByKind),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
updated.status.phase = nextPhase;
|
|
270
|
+
updated.status.lastTransitionAt = now;
|
|
271
|
+
|
|
272
|
+
return updated;
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
scanForRedaction(content) {
|
|
276
|
+
if (typeof content !== 'string' || content.length === 0) {
|
|
277
|
+
return { clean: true, redactedContent: content || '', redactionCount: 0, redactionsByKind: {} };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
let redacted = content;
|
|
281
|
+
const redactionsByKind = {};
|
|
282
|
+
let redactionCount = 0;
|
|
283
|
+
|
|
284
|
+
for (const { kind, pattern } of REDACTION_PATTERNS) {
|
|
285
|
+
const fresh = new RegExp(pattern.source, pattern.flags);
|
|
286
|
+
redacted = redacted.replace(fresh, (match) => {
|
|
287
|
+
redactionsByKind[kind] = (redactionsByKind[kind] || 0) + 1;
|
|
288
|
+
redactionCount++;
|
|
289
|
+
return `[REDACTED:${kind}]`;
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
clean: redactionCount === 0,
|
|
295
|
+
redactedContent: redacted,
|
|
296
|
+
redactionCount,
|
|
297
|
+
redactionsByKind,
|
|
298
|
+
};
|
|
299
|
+
},
|
|
300
|
+
|
|
301
|
+
validateOntology({ records, ontology }) {
|
|
302
|
+
const errors = [];
|
|
303
|
+
const requiredFields = ontology.requiredFields || {};
|
|
304
|
+
const allowedEdgeKinds = ontology.allowedEdgeKinds || [];
|
|
305
|
+
|
|
306
|
+
for (const record of records) {
|
|
307
|
+
const kind = record.nodeKind;
|
|
308
|
+
const fields = requiredFields[kind] || [];
|
|
309
|
+
|
|
310
|
+
for (const field of fields) {
|
|
311
|
+
const value = record.attributes?.[field];
|
|
312
|
+
if (value === undefined || value === null || value === '') {
|
|
313
|
+
errors.push({
|
|
314
|
+
record: record.id,
|
|
315
|
+
field,
|
|
316
|
+
message: `Missing required field '${field}' for nodeKind '${kind}'`,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Check edge kinds
|
|
322
|
+
for (const edge of (record.edges || [])) {
|
|
323
|
+
if (allowedEdgeKinds.length > 0 && !allowedEdgeKinds.includes(edge.kind)) {
|
|
324
|
+
errors.push({
|
|
325
|
+
record: record.id,
|
|
326
|
+
field: `edge.kind`,
|
|
327
|
+
message: `Edge kind '${edge.kind}' is not in allowedEdgeKinds`,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return { valid: errors.length === 0, errors };
|
|
334
|
+
},
|
|
335
|
+
|
|
336
|
+
createMemoryUpdate({ memoryRepository, sourceRun, changes, namespace = 'default', organizationRef = 'default' }) {
|
|
337
|
+
const now = new Date().toISOString();
|
|
338
|
+
const updateName = `memupdate-${sha256(memoryRepository + sourceRun + now).slice(0, 12)}`;
|
|
339
|
+
|
|
340
|
+
const update = createResource('AgentMemoryUpdate', { name: updateName, namespace }, {
|
|
341
|
+
organizationRef,
|
|
342
|
+
memoryRepository,
|
|
343
|
+
sourceRun,
|
|
344
|
+
changes: clone(changes),
|
|
345
|
+
});
|
|
346
|
+
update.status = { phase: 'Pending', createdAt: now };
|
|
347
|
+
|
|
348
|
+
return update;
|
|
349
|
+
},
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function globMatch(pattern, path) {
|
|
354
|
+
// Simple glob: convert * to regex .*
|
|
355
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*');
|
|
356
|
+
return new RegExp(`^${escaped}$`).test(path);
|
|
357
|
+
}
|