@twin3-ai/agent-id 0.1.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.
@@ -0,0 +1,306 @@
1
+ "use strict";
2
+
3
+ const fs = require("node:fs/promises");
4
+ const path = require("node:path");
5
+ const crypto = require("node:crypto");
6
+
7
+ const ALLOWED_PATHS = new Set([
8
+ "llms.txt",
9
+ "robots.txt",
10
+ ".well-known/agent-card.json",
11
+ ".well-known/agent-id.json",
12
+ ".well-known/aeo-agent.json",
13
+ ".well-known/agent-knowledge.json"
14
+ ]);
15
+ const SECRET_PATTERNS = [
16
+ /\bak_aeo_[A-Za-z0-9_-]{6,}\b/,
17
+ /\bav_[A-Za-z0-9_-]{8,}\b/,
18
+ /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/,
19
+ /-----BEGIN [A-Z ]+PRIVATE KEY-----/,
20
+ /AGENT_ID_RUNTIME_EVIDENCE_SECRET\s*[:=]/,
21
+ /(?:SITE_AGENT_KEY|AEO_AGENT_KEY)\s*[:=]\s*["'][^"']+["']/
22
+ ];
23
+
24
+ function connectorError(code, message) {
25
+ const error = new Error(message);
26
+ error.code = code;
27
+ return error;
28
+ }
29
+
30
+ function rootPath(value = process.cwd()) {
31
+ const root = path.resolve(String(value));
32
+ if (!path.isAbsolute(root)) throw connectorError("CONNECTOR_INVALID_ROOT", "Repository root must be absolute.");
33
+ return root;
34
+ }
35
+
36
+ function normalizeRelativePath(value) {
37
+ const relative = String(value || "").replace(/\\/g, "/").replace(/^\.\//, "");
38
+ if (!ALLOWED_PATHS.has(relative)) throw connectorError("CONNECTOR_PATH_NOT_ALLOWED", `Path is not on the connector allowlist: ${relative}`);
39
+ return relative;
40
+ }
41
+ async function safeAbsolute(root, relative) {
42
+ const normalized = normalizeRelativePath(relative);
43
+ const absolute = path.resolve(root, normalized);
44
+ const realRoot = await fs.realpath(root);
45
+ const parent = await fs.realpath(path.dirname(absolute)).catch((error) => {
46
+ if (error.code === "ENOENT") return path.resolve(realRoot, path.dirname(normalized));
47
+ throw error;
48
+ });
49
+ if (parent !== realRoot && !parent.startsWith(realRoot + path.sep)) throw connectorError("CONNECTOR_PATH_ESCAPE", "Connector path leaves repository root.");
50
+ const metadata = await fs.lstat(absolute).catch((error) => {
51
+ if (error.code === "ENOENT") return null;
52
+ throw error;
53
+ });
54
+ if (metadata && metadata.isSymbolicLink()) throw connectorError("CONNECTOR_SYMLINK_REJECTED", "Connector refuses symlink resources.");
55
+ return absolute;
56
+ }
57
+
58
+ function hashText(value) {
59
+ return `sha256:${crypto.createHash("sha256").update(value).digest("hex")}`;
60
+ }
61
+
62
+ function assertSafeContent(content) {
63
+ if (typeof content !== "string") throw connectorError("CONNECTOR_INVALID_CONTENT", "Connector content must be text.");
64
+ if (Buffer.byteLength(content, "utf8") > 1024 * 1024) throw connectorError("CONNECTOR_CONTENT_TOO_LARGE", "Connector content is limited to 1 MiB per file.");
65
+ if (SECRET_PATTERNS.some((pattern) => pattern.test(content))) throw connectorError("CONNECTOR_SECRET_DETECTED", "Connector content contains a credential-like value and was rejected.");
66
+ }
67
+
68
+ async function readText(filePath) {
69
+ try { return await fs.readFile(filePath, "utf8"); }
70
+ catch (error) { if (error.code === "ENOENT") return null; throw error; }
71
+ }
72
+
73
+ function lineDiff(relative, before, after) {
74
+ const oldLines = before == null ? [] : before.split(/(?<=\n)/);
75
+ const newLines = after.split(/(?<=\n)/);
76
+ const rows = [`--- a/${relative}`, `+++ b/${relative}`, "@@"];
77
+ for (const line of oldLines) rows.push(`-${line.replace(/\n$/, "")}`);
78
+ for (const line of newLines) rows.push(`+${line.replace(/\n$/, "")}`);
79
+ return rows.join("\n");
80
+ }
81
+
82
+ async function buildRepositoryPatch({ repositoryRoot = process.cwd(), changes = [] } = {}) {
83
+ const root = rootPath(repositoryRoot);
84
+ if (!Array.isArray(changes) || changes.length === 0) throw connectorError("CONNECTOR_CHANGES_REQUIRED", "At least one allowlisted change is required.");
85
+ const seen = new Set();
86
+ const normalized = [];
87
+ for (const change of changes) {
88
+ const relative = normalizeRelativePath(change && change.path);
89
+ if (seen.has(relative)) throw connectorError("CONNECTOR_DUPLICATE_PATH", `Path appears more than once: ${relative}`);
90
+ seen.add(relative);
91
+ assertSafeContent(change.content);
92
+ const absolute = await safeAbsolute(root, relative);
93
+ const before = await readText(absolute);
94
+ const after = change.content;
95
+ const beforeHash = before == null ? "" : hashText(before);
96
+ const afterHash = hashText(after);
97
+ normalized.push({
98
+ path: relative,
99
+ operation: before == null ? "add" : before === after ? "noop" : "update",
100
+ reason: String(change.reason || "").slice(0, 240),
101
+ before_sha256: beforeHash,
102
+ after_sha256: afterHash,
103
+ after_content: after,
104
+ diff: before === after ? `--- a/${relative}\n+++ b/${relative}\n@@\n(no change)` : lineDiff(relative, before, after)
105
+ });
106
+ }
107
+ const material = JSON.stringify({ root, changes: normalized.map(({ after_content, ...item }) => item) });
108
+ return {
109
+ schema: "agentx-repository-patch-plan-v1",
110
+ connector: "repository_patch",
111
+ status: "pending_approval",
112
+ repository_root: root,
113
+ approval_required: true,
114
+ default_branch_push: false,
115
+ prohibited_paths: ["package.json", ".env", "secrets", "pricing", "legal", "checkout", "database"],
116
+ plan_id: hashText(material).slice(0, 24),
117
+ changes: normalized
118
+ };
119
+ }
120
+
121
+ function deploymentChanges({ implementationBundle, knowledgePack } = {}) {
122
+ if (!implementationBundle || implementationBundle.schema !== "agentx-f1-implementation-bundle-v0.1") {
123
+ throw connectorError("CONNECTOR_INVALID_BUNDLE", "A valid F1 implementation bundle is required.");
124
+ }
125
+ const changes = [];
126
+ for (const file of implementationBundle.files || []) {
127
+ const relative = normalizeRelativePath(String(file && file.path || "").replace(/^\//, ""));
128
+ assertSafeContent(file && file.content);
129
+ changes.push({
130
+ path: relative,
131
+ content: file.content,
132
+ content_type: String(file.content_type || "text/plain").slice(0, 120),
133
+ reason: String(file.purpose || "Publish Agent-readable website asset").slice(0, 240)
134
+ });
135
+ }
136
+ if (knowledgePack != null) {
137
+ if (!knowledgePack || knowledgePack.schema !== "agentx-knowledge-pack-v1") {
138
+ throw connectorError("CONNECTOR_INVALID_KNOWLEDGE_PACK", "A valid Agent Knowledge Pack is required.");
139
+ }
140
+ const content = JSON.stringify(knowledgePack, null, 2) + "\n";
141
+ assertSafeContent(content);
142
+ changes.push({
143
+ path: ".well-known/agent-knowledge.json",
144
+ content,
145
+ content_type: "application/json; charset=utf-8",
146
+ reason: "Publish observed Agent Knowledge Pack"
147
+ });
148
+ }
149
+ if (!changes.length) throw connectorError("CONNECTOR_CHANGES_REQUIRED", "The implementation bundle contains no deployable files.");
150
+ return changes;
151
+ }
152
+
153
+ async function buildImplementationPatch({ repositoryRoot = process.cwd(), implementationBundle, knowledgePack } = {}) {
154
+ return buildRepositoryPatch({
155
+ repositoryRoot,
156
+ changes: deploymentChanges({ implementationBundle, knowledgePack })
157
+ });
158
+ }
159
+
160
+ function validatePlan(plan, expectedRoot) {
161
+ if (!plan || plan.schema !== "agentx-repository-patch-plan-v1" || plan.approval_required !== true) throw connectorError("CONNECTOR_INVALID_PLAN", "Invalid repository patch plan.");
162
+ const root = rootPath(plan.repository_root);
163
+ if (expectedRoot && root !== rootPath(expectedRoot)) throw connectorError("CONNECTOR_ROOT_MISMATCH", "Repository root does not match the patch plan.");
164
+ for (const change of plan.changes || []) {
165
+ normalizeRelativePath(change.path);
166
+ assertSafeContent(change.after_content);
167
+ }
168
+ return root;
169
+ }
170
+
171
+ async function writeAtomic(filePath, content) {
172
+ await fs.mkdir(path.dirname(filePath), { recursive: true, mode: 0o700 });
173
+ const temporary = `${filePath}.${process.pid}.${crypto.randomUUID()}.tmp`;
174
+ try {
175
+ await fs.writeFile(temporary, content, { encoding: "utf8", mode: 0o644 });
176
+ await fs.rename(temporary, filePath);
177
+ } finally {
178
+ await fs.rm(temporary, { force: true }).catch(() => {});
179
+ }
180
+ }
181
+
182
+ async function applyRepositoryPatch(plan, { approved = false } = {}) {
183
+ const root = validatePlan(plan);
184
+ if (approved !== true) throw connectorError("CONNECTOR_APPROVAL_REQUIRED", "Connector application requires explicit approved=true.");
185
+ const rollbackEntries = [];
186
+ const appliedChanges = [];
187
+ for (const change of plan.changes) {
188
+ const absolute = await safeAbsolute(root, change.path);
189
+ const current = await readText(absolute);
190
+ const currentHash = current == null ? "" : hashText(current);
191
+ if (currentHash !== change.before_sha256) throw connectorError("CONNECTOR_STALE_PLAN", `Repository changed since the plan was created: ${change.path}`);
192
+ rollbackEntries.push({ path: change.path, before_sha256: change.before_sha256, before_content: current, after_sha256: change.after_sha256 });
193
+ }
194
+ try {
195
+ for (const change of plan.changes) {
196
+ const absolute = await safeAbsolute(root, change.path);
197
+ if (change.operation !== "noop") {
198
+ await writeAtomic(absolute, change.after_content);
199
+ const verified = await readText(absolute);
200
+ if (verified == null || hashText(verified) !== change.after_sha256) throw connectorError("CONNECTOR_VERIFY_FAILED", `Post-write verification failed: ${change.path}`);
201
+ }
202
+ appliedChanges.push({ path: change.path, operation: change.operation, after_sha256: change.after_sha256 });
203
+ }
204
+ } catch (error) {
205
+ for (const item of rollbackEntries.slice(0, appliedChanges.length + 1).reverse()) {
206
+ const absolute = await safeAbsolute(root, item.path);
207
+ if (item.before_sha256) await writeAtomic(absolute, item.before_content);
208
+ else await fs.rm(absolute, { force: true });
209
+ }
210
+ throw error;
211
+ }
212
+ const receipt = {
213
+ schema: "agentx-repository-patch-receipt-v1",
214
+ connector: "repository_patch",
215
+ plan_id: plan.plan_id,
216
+ repository_root: root,
217
+ applied: true,
218
+ default_branch_push: false,
219
+ applied_changes: appliedChanges,
220
+ rollback_entries: rollbackEntries
221
+ };
222
+ const receiptPath = path.join(root, ".agent-id", "repository-patch-receipt.json");
223
+ await writeAtomic(receiptPath, JSON.stringify({ ...receipt, rollback_entries: rollbackEntries }, null, 2) + "\n");
224
+ return receipt;
225
+ }
226
+
227
+ async function rollbackRepositoryPatch(receiptOrRoot, { approved = false } = {}) {
228
+ if (approved !== true) throw connectorError("CONNECTOR_APPROVAL_REQUIRED", "Rollback requires explicit approved=true.");
229
+ const rootHint = typeof receiptOrRoot === "string" ? rootPath(receiptOrRoot) : "";
230
+ let receipt = typeof receiptOrRoot === "string" ? await (async () => {
231
+ const parsed = JSON.parse(await fs.readFile(path.join(rootHint, ".agent-id", "repository-patch-receipt.json"), "utf8"));
232
+ return parsed;
233
+ })() : receiptOrRoot;
234
+ if (!receipt || receipt.schema !== "agentx-repository-patch-receipt-v1") throw connectorError("CONNECTOR_INVALID_RECEIPT", "Invalid repository patch receipt.");
235
+ const root = rootPath(receipt.repository_root);
236
+ for (const item of receipt.rollback_entries || []) {
237
+ normalizeRelativePath(item.path);
238
+ if (item.before_sha256 && typeof item.before_content !== "string") throw connectorError("CONNECTOR_INVALID_RECEIPT", `Rollback content is missing for ${item.path}`);
239
+ if (item.before_sha256) assertSafeContent(item.before_content);
240
+ }
241
+ const restored = [];
242
+ for (const item of receipt.rollback_entries || []) {
243
+ const relative = normalizeRelativePath(item.path);
244
+ const absolute = await safeAbsolute(root, relative);
245
+ const current = await readText(absolute);
246
+ const currentHash = current == null ? "" : hashText(current);
247
+ if (currentHash !== item.after_sha256) throw connectorError("CONNECTOR_ROLLBACK_CONFLICT", `Repository changed after the patch: ${relative}`);
248
+ if (item.before_sha256) await writeAtomic(absolute, item.before_content);
249
+ else await fs.rm(absolute, { force: true });
250
+ restored.push({ path: relative, before_sha256: item.before_sha256 });
251
+ }
252
+ return { schema: "agentx-repository-rollback-receipt-v1", connector: "repository_patch", plan_id: receipt.plan_id || "", rolled_back: true, files: restored };
253
+ }
254
+
255
+ module.exports = {
256
+ ALLOWED_PATHS,
257
+ buildRepositoryPatch,
258
+ buildImplementationPatch,
259
+ deploymentChanges,
260
+ applyRepositoryPatch,
261
+ rollbackRepositoryPatch,
262
+ createRepositoryConnector
263
+ };
264
+
265
+ function createRepositoryConnector({ repositoryRoot = process.cwd() } = {}) {
266
+ const root = rootPath(repositoryRoot);
267
+ return Object.freeze({
268
+ describeCapabilities() {
269
+ return { connector: "repository_patch", operations: ["upsert", "replace"], paths: [...ALLOWED_PATHS].sort(), approval_required: true };
270
+ },
271
+ async readResource(resource) {
272
+ const relative = normalizeRelativePath(resource && resource.path);
273
+ const absolute = await safeAbsolute(root, relative);
274
+ const content = await readText(absolute);
275
+ return {
276
+ connector: "repository_patch",
277
+ environment: String(resource.environment || "production"),
278
+ resource_type: String(resource.resource_type || "file"),
279
+ resource_id: String(resource.resource_id || relative),
280
+ path: relative,
281
+ root_path: root,
282
+ real_path: absolute,
283
+ content,
284
+ sha256: content == null ? "" : hashText(content)
285
+ };
286
+ },
287
+ async planOperations(task, currentResource) {
288
+ const changes = task.operations.map((operation) => {
289
+ if (!["upsert", "replace"].includes(operation.operation)) throw connectorError("CONNECTOR_OPERATION_NOT_SUPPORTED", "Repository connector supports upsert and replace.");
290
+ return { path: operation.path, content: operation.value, reason: `Enterprise task ${task.task_id}` };
291
+ });
292
+ const plan = await buildRepositoryPatch({ repositoryRoot: root, changes });
293
+ if (plan.changes.some((change) => change.before_sha256 !== currentResource.sha256)) throw connectorError("CONNECTOR_STALE_PLAN", "Task expected hash does not match the current resource.");
294
+ return plan;
295
+ },
296
+ applyOperations(plan) { return applyRepositoryPatch(plan, { approved: true }); },
297
+ async verifyLocalResult(receipt) {
298
+ for (const item of receipt.applied_changes || []) {
299
+ const content = await readText(await safeAbsolute(root, item.path));
300
+ if (content == null || hashText(content) !== item.after_sha256) throw connectorError("CONNECTOR_VERIFY_FAILED", `Verification failed: ${item.path}`);
301
+ }
302
+ return { verified: true };
303
+ },
304
+ rollback(receipt) { return rollbackRepositoryPatch(receipt || root, { approved: true }); }
305
+ });
306
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+
3
+ const DEFAULT_ENDPOINT = "https://api.agentid.twin3.ai";
4
+ const TRUSTED_ISSUER_KEY_SHA256 = "sha256:f1dd1844319a02a8ec3c32552ed730fdda286e733cf6b5d54029ec7bd59fda1a";
5
+
6
+ function configuredEndpoint(env = process.env) {
7
+ return String(env.AGENT_ID_ENDPOINT || env.AEO_AGENT_ENDPOINT || DEFAULT_ENDPOINT).replace(/\/+$/, "");
8
+ }
9
+
10
+ module.exports = { DEFAULT_ENDPOINT, TRUSTED_ISSUER_KEY_SHA256, configuredEndpoint };