@twin3-ai/agent-id 0.2.0 → 0.3.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.
- package/b1-sync.js +85 -0
- package/bin/agent-id-b1-sync.js +44 -0
- package/bin/agent-id-cloudflare-a1.js +53 -0
- package/bin/agent-id.js +328 -75
- package/cloudflare-a1-sync.js +105 -0
- package/domain-proof.js +193 -0
- package/edge-html-injection.js +258 -0
- package/enterprise-identity.js +21 -0
- package/installer.js +565 -43
- package/package.json +12 -3
- package/production-preflight.js +35 -4
- package/release-verifier.js +46 -1
- package/repository-connector.js +123 -5
- package/site-agent.js +55 -3
- package/sync-service.js +1 -1
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twin3-ai/agent-id",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Domain-bound Agent identity, AEO evidence, and website-Agent integration SDK.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"private": false,
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/cis2042/agent-id.git"
|
|
10
|
+
},
|
|
7
11
|
"files": [
|
|
8
12
|
"bin",
|
|
9
13
|
"*.js"
|
|
@@ -16,7 +20,7 @@
|
|
|
16
20
|
"npm": ">=10"
|
|
17
21
|
},
|
|
18
22
|
"scripts": {
|
|
19
|
-
"prepublishOnly": "node --check bin/agent-id.js && node --check installer.js && node --check enterprise-identity.js && node --check release-verifier.js && node --check production-preflight.js && node --check static-edge-adapter.js && node --check optimization-loop.js && node --check artifact-bundle.js"
|
|
23
|
+
"prepublishOnly": "node --check bin/agent-id.js && node --check bin/agent-id-cloudflare-a1.js && node --check bin/agent-id-b1-sync.js && node --check installer.js && node --check enterprise-identity.js && node --check domain-proof.js && node --check release-verifier.js && node --check production-preflight.js && node --check static-edge-adapter.js && node --check edge-html-injection.js && node --check optimization-loop.js && node --check artifact-bundle.js && node --check cloudflare-a1-sync.js && node --check b1-sync.js"
|
|
20
24
|
},
|
|
21
25
|
"main": "site-agent.js",
|
|
22
26
|
"exports": {
|
|
@@ -31,10 +35,13 @@
|
|
|
31
35
|
"./local-policy": "./local-policy.js",
|
|
32
36
|
"./task-executor": "./task-executor.js",
|
|
33
37
|
"./telemetry-collector": "./telemetry-collector.js",
|
|
38
|
+
"./cloudflare-a1-sync": "./cloudflare-a1-sync.js",
|
|
39
|
+
"./b1-sync": "./b1-sync.js",
|
|
34
40
|
"./trust-verifier": "./trust-verifier.js",
|
|
35
41
|
"./repository-connector": "./repository-connector.js",
|
|
36
42
|
"./cloudflare-worker-adapter": "./cloudflare-worker-adapter.js",
|
|
37
43
|
"./static-edge-adapter": "./static-edge-adapter.js",
|
|
44
|
+
"./edge-html-injection": "./edge-html-injection.js",
|
|
38
45
|
"./optimization-loop": "./optimization-loop.js",
|
|
39
46
|
"./artifact-bundle": "./artifact-bundle.js",
|
|
40
47
|
"./release-verifier": "./release-verifier.js",
|
|
@@ -42,6 +49,8 @@
|
|
|
42
49
|
},
|
|
43
50
|
"bin": {
|
|
44
51
|
"agent-id": "bin/agent-id.js",
|
|
45
|
-
"agent-id-sync": "bin/agent-id-sync.js"
|
|
52
|
+
"agent-id-sync": "bin/agent-id-sync.js",
|
|
53
|
+
"agent-id-cloudflare-a1": "bin/agent-id-cloudflare-a1.js",
|
|
54
|
+
"agent-id-b1-sync": "bin/agent-id-b1-sync.js"
|
|
46
55
|
}
|
|
47
56
|
}
|
package/production-preflight.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const net = require("node:net");
|
|
4
|
-
const { verifyReleaseManifest } = require("./release-verifier.js");
|
|
4
|
+
const { verifyReleaseManifest, verifyProductionReleaseReceipt, sha256 } = require("./release-verifier.js");
|
|
5
5
|
|
|
6
6
|
const PACKAGE_NAME = "@twin3-ai/agent-id";
|
|
7
7
|
|
|
@@ -50,12 +50,15 @@ async function runProductionPreflight({ endpoint, packageVersion, expectedIssuer
|
|
|
50
50
|
|
|
51
51
|
const packageId = encodeURIComponent(PACKAGE_NAME);
|
|
52
52
|
const version = String(packageVersion || "");
|
|
53
|
-
const [manifestResponse, issuerResponse, registryResponse, healthResponse, cardResponse] = await Promise.all([
|
|
53
|
+
const [manifestResponse, receiptResponse, issuerResponse, registryResponse, healthResponse, cardResponse, installResponse, openapiResponse] = await Promise.all([
|
|
54
54
|
fetchJson(fetchImpl, `${origin}/.well-known/agent-id-release.json`),
|
|
55
|
+
fetchJson(fetchImpl, `${origin}/.well-known/agent-id-release-receipt.json`),
|
|
55
56
|
fetchJson(fetchImpl, `${origin}/.well-known/agentx-issuer-key.json`),
|
|
56
57
|
fetchJson(fetchImpl, `https://registry.npmjs.org/${packageId}/${encodeURIComponent(version)}`),
|
|
57
58
|
fetchJson(fetchImpl, `${origin}/agent/health.json`),
|
|
58
59
|
fetchJson(fetchImpl, `${origin}/.well-known/agent-card.json`),
|
|
60
|
+
fetchJson(fetchImpl, `${origin}/.well-known/xagent-install.json`),
|
|
61
|
+
fetchJson(fetchImpl, `${origin}/agent/openapi.json`),
|
|
59
62
|
]);
|
|
60
63
|
|
|
61
64
|
const manifest = manifestResponse.body;
|
|
@@ -79,13 +82,32 @@ async function runProductionPreflight({ endpoint, packageVersion, expectedIssuer
|
|
|
79
82
|
&& registry.dist.integrity === manifest?.package?.integrity;
|
|
80
83
|
const health = healthResponse.ok && healthResponse.body && healthResponse.body.ok !== false;
|
|
81
84
|
const agentCard = cardResponse.ok && typeof cardResponse.body?.name === "string" && cardResponse.body.name.length > 0;
|
|
82
|
-
const
|
|
85
|
+
const installManifest = installResponse.ok && installResponse.body?.schema === "xagent-install-manifest-v1";
|
|
86
|
+
const openapi = openapiResponse.ok && openapiResponse.body?.openapi === "3.1.0";
|
|
87
|
+
const productionReceipt = receiptResponse.ok && receiptResponse.body
|
|
88
|
+
? verifyProductionReleaseReceipt({
|
|
89
|
+
receipt: receiptResponse.body,
|
|
90
|
+
issuerDescriptor: issuerResponse.body,
|
|
91
|
+
releaseManifest: manifest,
|
|
92
|
+
expectedOrigin: origin,
|
|
93
|
+
packageVersion: version,
|
|
94
|
+
expectedIssuerKeySha256,
|
|
95
|
+
})
|
|
96
|
+
: { ok: false, signature_verified: false, blockers: Array.isArray(receiptResponse.body?.blockers) ? receiptResponse.body.blockers : [] };
|
|
97
|
+
const artifactBindings = productionReceipt.ok
|
|
98
|
+
&& receiptResponse.body?.artifacts?.agent_card?.sha256 === sha256(cardResponse.body)
|
|
99
|
+
&& receiptResponse.body?.artifacts?.install_manifest?.sha256 === sha256(installResponse.body)
|
|
100
|
+
&& receiptResponse.body?.artifacts?.openapi?.sha256 === sha256(openapiResponse.body);
|
|
101
|
+
const apiReachable = [manifestResponse, receiptResponse, issuerResponse, healthResponse, cardResponse, installResponse, openapiResponse]
|
|
83
102
|
.some(response => Number(response.status) > 0);
|
|
84
103
|
const checks = {
|
|
85
104
|
stable_api_origin_format: true,
|
|
86
105
|
api_https_reachable: apiReachable,
|
|
87
106
|
stable_api_origin: apiReachable,
|
|
88
107
|
release_manifest_http: manifestResponse.ok,
|
|
108
|
+
production_receipt_http: receiptResponse.ok,
|
|
109
|
+
production_receipt_signature: productionReceipt.signature_verified === true,
|
|
110
|
+
production_release_binding: productionReceipt.ok === true && artifactBindings,
|
|
89
111
|
issuer_descriptor_http: issuerResponse.ok,
|
|
90
112
|
release_signature: release.signature_verified === true,
|
|
91
113
|
release_contract: release.ok === true,
|
|
@@ -93,15 +115,21 @@ async function runProductionPreflight({ endpoint, packageVersion, expectedIssuer
|
|
|
93
115
|
registry_integrity: registryIntegrity,
|
|
94
116
|
health_endpoint: health,
|
|
95
117
|
agent_card: agentCard,
|
|
118
|
+
install_manifest: installManifest,
|
|
119
|
+
openapi,
|
|
96
120
|
};
|
|
97
|
-
const blockers = [...release.blockers];
|
|
121
|
+
const blockers = [...release.blockers, ...productionReceipt.blockers];
|
|
98
122
|
if (!apiReachable) blockers.push("stable_api_unreachable");
|
|
99
123
|
if (apiReachable && !manifestResponse.ok) blockers.push("release_manifest_unavailable");
|
|
124
|
+
if (apiReachable && !receiptResponse.ok) blockers.push("production_release_receipt_unavailable");
|
|
100
125
|
if (apiReachable && !issuerResponse.ok) blockers.push("issuer_descriptor_unavailable");
|
|
101
126
|
if (!registryIdentity) blockers.push("npm_package_unpublished_or_version_mismatch");
|
|
102
127
|
if (manifestResponse.ok && registryIdentity && !registryIntegrity) blockers.push("npm_integrity_mismatch");
|
|
103
128
|
if (apiReachable && !health) blockers.push("health_endpoint_unavailable");
|
|
104
129
|
if (apiReachable && !agentCard) blockers.push("agent_card_unavailable");
|
|
130
|
+
if (apiReachable && !installManifest) blockers.push("install_manifest_unavailable");
|
|
131
|
+
if (apiReachable && !openapi) blockers.push("openapi_unavailable");
|
|
132
|
+
if (productionReceipt.ok && !artifactBindings) blockers.push("production_artifact_hash_mismatch");
|
|
105
133
|
|
|
106
134
|
return {
|
|
107
135
|
ok: blockers.length === 0,
|
|
@@ -116,6 +144,9 @@ async function runProductionPreflight({ endpoint, packageVersion, expectedIssuer
|
|
|
116
144
|
published_at: manifest?.published_at || "",
|
|
117
145
|
integrity: manifest?.package?.integrity || "",
|
|
118
146
|
signature_verified: release.signature_verified === true,
|
|
147
|
+
production_receipt_signature_verified: productionReceipt.signature_verified === true,
|
|
148
|
+
production_revision: receiptResponse.body?.deployment?.revision || "",
|
|
149
|
+
production_image_digest: receiptResponse.body?.deployment?.image_digest || "",
|
|
119
150
|
},
|
|
120
151
|
};
|
|
121
152
|
}
|
package/release-verifier.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const crypto = require("node:crypto");
|
|
4
4
|
|
|
5
5
|
const SCHEMA = "agentx-release-manifest-v1";
|
|
6
|
+
const PRODUCTION_RECEIPT_SCHEMA = "agentx-production-release-receipt-v1";
|
|
6
7
|
const PACKAGE_NAME = "@twin3-ai/agent-id";
|
|
7
8
|
|
|
8
9
|
function stableValue(value) {
|
|
@@ -80,4 +81,48 @@ function verifyReleaseManifest({ manifest, issuerDescriptor, expectedOrigin, pac
|
|
|
80
81
|
};
|
|
81
82
|
}
|
|
82
83
|
|
|
83
|
-
|
|
84
|
+
function verifyProductionReleaseReceipt({ receipt, issuerDescriptor, releaseManifest, expectedOrigin, packageVersion, expectedIssuerKeySha256 }) {
|
|
85
|
+
const blockers = [];
|
|
86
|
+
const expected = normalizedOrigin(expectedOrigin);
|
|
87
|
+
if (!receipt || receipt.schema !== PRODUCTION_RECEIPT_SCHEMA) blockers.push("invalid_production_receipt_schema");
|
|
88
|
+
if (!receipt || receipt.ready !== true) blockers.push(...(Array.isArray(receipt?.blockers) ? receipt.blockers : ["production_receipt_not_ready"]));
|
|
89
|
+
if (!expected || normalizedOrigin(receipt?.api_origin) !== expected) blockers.push("production_receipt_origin_mismatch");
|
|
90
|
+
if (receipt?.package?.name !== PACKAGE_NAME || receipt?.package?.version !== String(packageVersion || "")) blockers.push("production_receipt_package_mismatch");
|
|
91
|
+
if (receipt?.package?.integrity !== releaseManifest?.package?.integrity) blockers.push("production_receipt_integrity_mismatch");
|
|
92
|
+
if (receipt?.source?.release_commit !== releaseManifest?.release_commit) blockers.push("production_receipt_commit_mismatch");
|
|
93
|
+
if (receipt?.source?.release_manifest_sha256 !== releaseManifest?.content_hash) blockers.push("production_receipt_manifest_hash_mismatch");
|
|
94
|
+
if (!/^sha256:[0-9a-f]{64}$/.test(String(receipt?.deployment?.image_digest || ""))) blockers.push("production_receipt_image_digest_missing");
|
|
95
|
+
if (!String(receipt?.deployment?.revision || "").startsWith(`${String(receipt?.deployment?.service || "")}-`)) blockers.push("production_receipt_revision_mismatch");
|
|
96
|
+
for (const name of ["agent_card", "install_manifest", "openapi"]) {
|
|
97
|
+
if (!/^sha256:[0-9a-f]{64}$/.test(String(receipt?.artifacts?.[name]?.sha256 || ""))) blockers.push(`production_receipt_${name}_hash_missing`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const proof = receipt && typeof receipt.proof === "object" ? receipt.proof : {};
|
|
101
|
+
const unsigned = receipt && typeof receipt === "object"
|
|
102
|
+
? Object.fromEntries(Object.entries(receipt).filter(([key]) => !["proof", "content_hash"].includes(key)))
|
|
103
|
+
: {};
|
|
104
|
+
if (receipt?.content_hash !== sha256(unsigned)) blockers.push("production_receipt_content_hash_mismatch");
|
|
105
|
+
if (proof.verification_method !== `${expected}/.well-known/agentx-issuer-key.json#ed25519-2026`) blockers.push("production_receipt_verification_method_mismatch");
|
|
106
|
+
|
|
107
|
+
let signatureVerified = false;
|
|
108
|
+
const publicKey = String(issuerDescriptor?.public_key_pem || "");
|
|
109
|
+
if (!publicKey) {
|
|
110
|
+
blockers.push("issuer_public_key_missing");
|
|
111
|
+
} else {
|
|
112
|
+
const issuerKeySha256 = `sha256:${crypto.createHash("sha256").update(publicKey).digest("hex")}`;
|
|
113
|
+
if (!expectedIssuerKeySha256 || issuerKeySha256 !== expectedIssuerKeySha256) blockers.push("issuer_key_pin_mismatch");
|
|
114
|
+
try {
|
|
115
|
+
signatureVerified = crypto.verify(null, Buffer.from(canonical(unsigned)), crypto.createPublicKey(publicKey), decodeProof(proof.proof_value));
|
|
116
|
+
} catch (_error) {
|
|
117
|
+
signatureVerified = false;
|
|
118
|
+
}
|
|
119
|
+
if (!signatureVerified) blockers.push("production_receipt_signature_invalid");
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
ok: blockers.length === 0,
|
|
123
|
+
signature_verified: signatureVerified,
|
|
124
|
+
blockers: [...new Set(blockers)],
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports = { verifyReleaseManifest, verifyProductionReleaseReceipt, canonical, sha256 };
|
package/repository-connector.js
CHANGED
|
@@ -11,8 +11,22 @@ const AUTO_MANAGED_PATHS = new Set([
|
|
|
11
11
|
".well-known/aeo-agent.json",
|
|
12
12
|
".well-known/agent-knowledge.json"
|
|
13
13
|
]);
|
|
14
|
+
// A missing sitemap may be created, but an existing customer sitemap is never
|
|
15
|
+
// replaced by the generic connector. Replacing one could silently remove product,
|
|
16
|
+
// locale, image, or news URLs that this service has not observed.
|
|
17
|
+
const CREATE_ONLY_PATHS = new Set(["sitemap.xml"]);
|
|
18
|
+
// A full robots.txt rewrite stays owner-gated: a bad directive can delist an entire
|
|
19
|
+
// site. `buildRobotsAdditivePatch` below is the only supported automated path, and it
|
|
20
|
+
// can append AI crawler directives without ever touching an existing line.
|
|
14
21
|
const REVIEW_ONLY_PATHS = new Set(["robots.txt"]);
|
|
15
|
-
const ALLOWED_PATHS = AUTO_MANAGED_PATHS;
|
|
22
|
+
const ALLOWED_PATHS = new Set([...AUTO_MANAGED_PATHS, ...CREATE_ONLY_PATHS]);
|
|
23
|
+
|
|
24
|
+
const AI_CRAWLER_BLOCK_MARKER = "# agent-id: AI crawler directives (additive, generated)";
|
|
25
|
+
const KNOWN_AI_AGENTS = new Set([
|
|
26
|
+
"GPTBot", "OAI-SearchBot", "ChatGPT-User", "ClaudeBot", "Claude-Web",
|
|
27
|
+
"PerplexityBot", "Google-Extended", "Applebot-Extended", "CCBot",
|
|
28
|
+
"Bytespider", "meta-externalagent", "Amazonbot", "cohere-ai"
|
|
29
|
+
]);
|
|
16
30
|
const SECRET_PATTERNS = [
|
|
17
31
|
/\bak_aeo_[A-Za-z0-9_-]{6,}\b/,
|
|
18
32
|
/\bav_[A-Za-z0-9_-]{8,}\b/,
|
|
@@ -94,6 +108,9 @@ async function buildRepositoryPatch({ repositoryRoot = process.cwd(), changes =
|
|
|
94
108
|
const absolute = await safeAbsolute(root, relative);
|
|
95
109
|
const before = await readText(absolute);
|
|
96
110
|
const after = change.content;
|
|
111
|
+
if (CREATE_ONLY_PATHS.has(relative) && before != null && before !== after) {
|
|
112
|
+
throw connectorError("CONNECTOR_CREATE_ONLY_PATH_EXISTS", `Existing customer file cannot be replaced automatically: ${relative}`);
|
|
113
|
+
}
|
|
97
114
|
const beforeHash = before == null ? "" : hashText(before);
|
|
98
115
|
const afterHash = hashText(after);
|
|
99
116
|
normalized.push({
|
|
@@ -153,10 +170,27 @@ function deploymentChanges({ implementationBundle, knowledgePack } = {}) {
|
|
|
153
170
|
}
|
|
154
171
|
|
|
155
172
|
async function buildImplementationPatch({ repositoryRoot = process.cwd(), implementationBundle, knowledgePack } = {}) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
173
|
+
const root = rootPath(repositoryRoot);
|
|
174
|
+
const changes = deploymentChanges({ implementationBundle, knowledgePack });
|
|
175
|
+
const safeChanges = [];
|
|
176
|
+
const skippedChanges = [];
|
|
177
|
+
for (const change of changes) {
|
|
178
|
+
if (CREATE_ONLY_PATHS.has(change.path)) {
|
|
179
|
+
const current = await readText(await safeAbsolute(root, change.path));
|
|
180
|
+
if (current != null && current !== change.content) {
|
|
181
|
+
skippedChanges.push({
|
|
182
|
+
path: change.path,
|
|
183
|
+
status: "requires_customer_review",
|
|
184
|
+
reason: "existing_customer_file_preserved",
|
|
185
|
+
current_sha256: hashText(current)
|
|
186
|
+
});
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
safeChanges.push(change);
|
|
191
|
+
}
|
|
192
|
+
const plan = await buildRepositoryPatch({ repositoryRoot: root, changes: safeChanges });
|
|
193
|
+
return { ...plan, skipped_changes: skippedChanges };
|
|
160
194
|
}
|
|
161
195
|
|
|
162
196
|
async function buildVerifiedArtifactPatch({ repositoryRoot = process.cwd(), artifactBundle, issuerPublicKey, expectedSubject, now } = {}) {
|
|
@@ -276,10 +310,94 @@ async function rollbackRepositoryPatch(receiptOrRoot, { approved = false } = {})
|
|
|
276
310
|
return { schema: "agentx-repository-rollback-receipt-v1", connector: "repository_patch", plan_id: receipt.plan_id || "", artifact_bundle_hash: String(receipt.artifact_bundle_hash || ""), rolled_back: true, files: restored };
|
|
277
311
|
}
|
|
278
312
|
|
|
313
|
+
/**
|
|
314
|
+
* Append AI crawler directives to robots.txt without ever removing or rewriting an
|
|
315
|
+
* existing line.
|
|
316
|
+
*
|
|
317
|
+
* A full robots.txt rewrite remains review-only because a single bad `Disallow: /`
|
|
318
|
+
* can delist an entire site. This additive-only mode is narrow enough to automate:
|
|
319
|
+
* it appends a clearly marked block, refuses any removal, refuses a global disallow,
|
|
320
|
+
* and is idempotent so repeated runs converge on the same file.
|
|
321
|
+
*/
|
|
322
|
+
function buildRobotsAdditivePatch({ current = "", allowAgents = [], removeLines = [], disallowAll = false } = {}) {
|
|
323
|
+
if (Array.isArray(removeLines) && removeLines.length) {
|
|
324
|
+
throw connectorError("ROBOTS_REMOVAL_FORBIDDEN", "Additive robots mode cannot remove or rewrite existing directives.");
|
|
325
|
+
}
|
|
326
|
+
if (disallowAll === true) {
|
|
327
|
+
throw connectorError("ROBOTS_GLOBAL_DISALLOW_FORBIDDEN", "Additive robots mode refuses a global disallow directive.");
|
|
328
|
+
}
|
|
329
|
+
const before = typeof current === "string" ? current : "";
|
|
330
|
+
assertSafeContent(before);
|
|
331
|
+
|
|
332
|
+
const agents = [];
|
|
333
|
+
const seen = new Set();
|
|
334
|
+
for (const value of Array.isArray(allowAgents) ? allowAgents : [allowAgents]) {
|
|
335
|
+
const agent = String(value || "").trim();
|
|
336
|
+
if (!agent || seen.has(agent)) continue;
|
|
337
|
+
if (agent === "*") {
|
|
338
|
+
throw connectorError("ROBOTS_GLOBAL_DISALLOW_FORBIDDEN", "Additive robots mode refuses wildcard agent directives.");
|
|
339
|
+
}
|
|
340
|
+
if (!KNOWN_AI_AGENTS.has(agent)) {
|
|
341
|
+
throw connectorError("ROBOTS_AGENT_NOT_RECOGNIZED", `Agent is not a recognized AI crawler: ${agent}`);
|
|
342
|
+
}
|
|
343
|
+
seen.add(agent);
|
|
344
|
+
agents.push(agent);
|
|
345
|
+
}
|
|
346
|
+
if (!agents.length) throw connectorError("ROBOTS_AGENTS_REQUIRED", "At least one recognized AI crawler is required.");
|
|
347
|
+
|
|
348
|
+
const existingAgents = new Set(
|
|
349
|
+
(before.match(/^\s*User-agent:\s*(.+)$/gim) || []).map((line) => line.split(":").slice(1).join(":").trim())
|
|
350
|
+
);
|
|
351
|
+
const pending = agents.filter((agent) => !existingAgents.has(agent));
|
|
352
|
+
|
|
353
|
+
let after = before;
|
|
354
|
+
const appendedLines = [];
|
|
355
|
+
if (pending.length) {
|
|
356
|
+
const block = [before.endsWith("\n") || before === "" ? "" : "\n", "\n", `${AI_CRAWLER_BLOCK_MARKER}\n`];
|
|
357
|
+
for (const agent of pending) {
|
|
358
|
+
block.push(`User-agent: ${agent}\n`, "Allow: /\n");
|
|
359
|
+
appendedLines.push(`User-agent: ${agent}`, "Allow: /");
|
|
360
|
+
}
|
|
361
|
+
after = before + block.join("");
|
|
362
|
+
assertSafeContent(after);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const beforeLines = before.split("\n");
|
|
366
|
+
const afterLines = after.split("\n");
|
|
367
|
+
const removed = beforeLines.filter((line) => line.trim() && !afterLines.includes(line));
|
|
368
|
+
if (removed.length) {
|
|
369
|
+
throw connectorError("ROBOTS_REMOVAL_FORBIDDEN", "Additive robots mode must preserve every existing line.");
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return {
|
|
373
|
+
schema: "agentx-robots-additive-patch-v1",
|
|
374
|
+
path: "robots.txt",
|
|
375
|
+
mode: "additive_only",
|
|
376
|
+
approval_required: true,
|
|
377
|
+
before,
|
|
378
|
+
after,
|
|
379
|
+
before_sha256: before === "" ? "" : hashText(before),
|
|
380
|
+
after_sha256: hashText(after),
|
|
381
|
+
appended_lines: appendedLines,
|
|
382
|
+
removed_lines: [],
|
|
383
|
+
agents: pending,
|
|
384
|
+
closes_checks: ["aeo_ai_crawler_policy", "seo_robots"],
|
|
385
|
+
non_claims: [
|
|
386
|
+
"never_removes_or_rewrites_existing_directives",
|
|
387
|
+
"never_emits_a_global_disallow",
|
|
388
|
+
"does_not_guarantee_ai_crawler_compliance"
|
|
389
|
+
]
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
279
393
|
module.exports = {
|
|
280
394
|
ALLOWED_PATHS,
|
|
281
395
|
AUTO_MANAGED_PATHS,
|
|
396
|
+
CREATE_ONLY_PATHS,
|
|
282
397
|
REVIEW_ONLY_PATHS,
|
|
398
|
+
AI_CRAWLER_BLOCK_MARKER,
|
|
399
|
+
KNOWN_AI_AGENTS,
|
|
400
|
+
buildRobotsAdditivePatch,
|
|
283
401
|
buildRepositoryPatch,
|
|
284
402
|
buildImplementationPatch,
|
|
285
403
|
buildVerifiedArtifactPatch,
|
package/site-agent.js
CHANGED
|
@@ -330,6 +330,40 @@ function createSiteAgentClient(options = {}) {
|
|
|
330
330
|
return data;
|
|
331
331
|
}
|
|
332
332
|
|
|
333
|
+
async function permissionsRequest(method, payload = {}) {
|
|
334
|
+
const key = payload.agent_key || payload.agentKey || agentKey;
|
|
335
|
+
const target = payload.url || payload.website || payload.host || "";
|
|
336
|
+
if (!key) {
|
|
337
|
+
throw siteAgentError(
|
|
338
|
+
"SITE_AGENT_KEY_REQUIRED",
|
|
339
|
+
"A Site Agent Key is required to manage this website agent's permissions."
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
const body = { ...payload };
|
|
343
|
+
delete body.agent_key;
|
|
344
|
+
delete body.agentKey;
|
|
345
|
+
const suffix = method === "GET" ? `?url=${encodeURIComponent(target)}` : "";
|
|
346
|
+
const request = {
|
|
347
|
+
method,
|
|
348
|
+
headers: {
|
|
349
|
+
"authorization": `Bearer ${key}`,
|
|
350
|
+
...(method === "POST" ? { "content-type": "application/json" } : {})
|
|
351
|
+
},
|
|
352
|
+
...(method === "POST" ? { body: JSON.stringify(body) } : {})
|
|
353
|
+
};
|
|
354
|
+
const res = await fetchWithRetry(endpoint + "/api/site_agents/permissions" + suffix, request);
|
|
355
|
+
const data = await readJsonResponse(res);
|
|
356
|
+
if (!res.ok || data.ok === false) {
|
|
357
|
+
const error = siteAgentError(
|
|
358
|
+
data.error || "SITE_AGENT_PERMISSION_FAILED",
|
|
359
|
+
data.message || data.error || `Site Agent permission request failed with HTTP ${res.status}`
|
|
360
|
+
);
|
|
361
|
+
error.status = res.status;
|
|
362
|
+
throw error;
|
|
363
|
+
}
|
|
364
|
+
return data;
|
|
365
|
+
}
|
|
366
|
+
|
|
333
367
|
return Object.freeze({
|
|
334
368
|
endpoint,
|
|
335
369
|
policy: SITE_AGENT_KEY_POLICY,
|
|
@@ -349,6 +383,12 @@ function createSiteAgentClient(options = {}) {
|
|
|
349
383
|
delete body.agentKey;
|
|
350
384
|
return publicPost("/api/site_agents/verify", body);
|
|
351
385
|
},
|
|
386
|
+
permissions(payload = {}) {
|
|
387
|
+
return permissionsRequest("GET", payload);
|
|
388
|
+
},
|
|
389
|
+
updatePermissions(payload = {}) {
|
|
390
|
+
return permissionsRequest("POST", payload);
|
|
391
|
+
},
|
|
352
392
|
insightsFeed(payload = {}) {
|
|
353
393
|
return call("insights_feed", payload);
|
|
354
394
|
},
|
|
@@ -362,7 +402,7 @@ function createSiteAgentClient(options = {}) {
|
|
|
362
402
|
ts: payload.ts == null ? Math.floor(Date.now() / 1000) : payload.ts,
|
|
363
403
|
data: {
|
|
364
404
|
runtime: payload.runtime || "server_side_site_agent",
|
|
365
|
-
sdk_version: payload.sdk_version || payload.sdkVersion || "0.
|
|
405
|
+
sdk_version: payload.sdk_version || payload.sdkVersion || "0.3.0"
|
|
366
406
|
}
|
|
367
407
|
}]
|
|
368
408
|
});
|
|
@@ -379,7 +419,7 @@ function createSiteAgentClient(options = {}) {
|
|
|
379
419
|
ts: payload.ts == null ? Math.floor(Date.now() / 1000) : payload.ts,
|
|
380
420
|
data: {
|
|
381
421
|
runtime: payload.runtime || "server_side_site_agent",
|
|
382
|
-
sdk_version: payload.sdk_version || payload.sdkVersion || "0.
|
|
422
|
+
sdk_version: payload.sdk_version || payload.sdkVersion || "0.3.0"
|
|
383
423
|
}
|
|
384
424
|
}]
|
|
385
425
|
});
|
|
@@ -792,6 +832,15 @@ function createSiteAgentClient(options = {}) {
|
|
|
792
832
|
monthlyDashboard(payload = {}) {
|
|
793
833
|
return call("monthly_dashboard", payload);
|
|
794
834
|
},
|
|
835
|
+
measurementSnapshot(payload = {}) {
|
|
836
|
+
return call("measurement_snapshot", payload);
|
|
837
|
+
},
|
|
838
|
+
measurementHistory(payload = {}) {
|
|
839
|
+
return call("measurement_history", payload);
|
|
840
|
+
},
|
|
841
|
+
measurementComparison(payload = {}) {
|
|
842
|
+
return call("measurement_comparison", payload);
|
|
843
|
+
},
|
|
795
844
|
async monthlyEvidenceLoop(payload = {}) {
|
|
796
845
|
const contentPackRes = await call("content_pack", payload);
|
|
797
846
|
const reviewRes = await publicPost("/api/agent/content_workflow_review", {
|
|
@@ -1006,5 +1055,8 @@ function createSiteAgentClient(options = {}) {
|
|
|
1006
1055
|
module.exports = {
|
|
1007
1056
|
SITE_AGENT_KEY_POLICY,
|
|
1008
1057
|
createSiteAgentClient,
|
|
1009
|
-
createTelemetryCollector: (options) => require("./telemetry-collector.js").createTelemetryCollector(options)
|
|
1058
|
+
createTelemetryCollector: (options) => require("./telemetry-collector.js").createTelemetryCollector(options),
|
|
1059
|
+
createCloudflareA1Sync: (options) => require("./cloudflare-a1-sync.js").createCloudflareA1Sync(options),
|
|
1060
|
+
createGa4B1Sync: (options) => require("./b1-sync.js").createGa4B1Sync(options),
|
|
1061
|
+
createOrderB1Sync: (options) => require("./b1-sync.js").createOrderB1Sync(options)
|
|
1010
1062
|
};
|
package/sync-service.js
CHANGED
|
@@ -125,7 +125,7 @@ function createSiteAgentSyncService(options = {}) {
|
|
|
125
125
|
const intervalMs = Math.max(1000, Number(options.intervalMs || process.env.AGENT_ID_SYNC_INTERVAL_MS || 900000));
|
|
126
126
|
const heartbeatIntervalMs = Math.max(300000, Number(options.heartbeatIntervalMs || process.env.AGENT_ID_HEARTBEAT_INTERVAL_MS || options.intervalMs || 900000));
|
|
127
127
|
const collector = options.collector || null;
|
|
128
|
-
const runtimeVersion = String(options.runtimeVersion || process.env.AGENT_ID_RUNTIME_VERSION || "0.
|
|
128
|
+
const runtimeVersion = String(options.runtimeVersion || process.env.AGENT_ID_RUNTIME_VERSION || "0.3.0");
|
|
129
129
|
const capabilities = Array.from(new Set((Array.isArray(options.capabilities) ? options.capabilities : ["heartbeat", "insights_feed"]).map((item) => String(item).trim()).filter(Boolean))).sort();
|
|
130
130
|
const now = options.now || Date.now;
|
|
131
131
|
const sleep = options.sleep || ((ms, signal) => new Promise((resolve) => {
|