@twin3-ai/agent-id 0.1.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/artifact-bundle.js +92 -0
- 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 +400 -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 +48 -0
- package/installer.js +679 -46
- package/local-policy.js +4 -2
- package/optimization-loop.js +48 -0
- package/package.json +15 -3
- package/production-preflight.js +35 -4
- package/release-verifier.js +46 -1
- package/repository-connector.js +150 -5
- package/site-agent.js +55 -3
- package/static-edge-adapter.js +115 -0
- package/sync-service.js +1 -1
- package/task-executor.js +80 -10
package/bin/agent-id.js
CHANGED
|
@@ -3,16 +3,29 @@ const { configuredEndpoint, TRUSTED_ISSUER_KEY_SHA256 } = require("../runtime-co
|
|
|
3
3
|
const endpoint = configuredEndpoint();
|
|
4
4
|
const fs = require("fs");
|
|
5
5
|
const path = require("path");
|
|
6
|
-
const { buildInstallPlan, resumeInstall, uninstallInstall, enterpriseInstallStatus, prepareEnterpriseSecretDirectory, writeEnterpriseBootstrapCredential, readEnterpriseBootstrapCredential, replaceEnterpriseBootstrapCredential, removeEnterpriseBootstrapCredential, writeEnterpriseEnrollmentState } = require("../installer.js");
|
|
6
|
+
const { buildInstallPlan, preflightInstallPlan, applyInstallPlan, resumeInstall, uninstallInstall, enterpriseInstallStatus, prepareEnterpriseSecretDirectory, writeEnterpriseBootstrapCredential, readEnterpriseBootstrapCredential, replaceEnterpriseBootstrapCredential, removeEnterpriseBootstrapCredential, writeEnterpriseEnrollmentState } = require("../installer.js");
|
|
7
7
|
const { buildRepositoryPatch, applyRepositoryPatch, rollbackRepositoryPatch } = require("../repository-connector.js");
|
|
8
|
+
const { createRepositoryConnector } = require("../repository-connector.js");
|
|
8
9
|
const { generateSiteAgentIdentity, createEnterpriseSession } = require("../enterprise-identity.js");
|
|
9
|
-
const { buildWellKnownProofDocument, resolveProofTarget, writeWellKnownProofDocument } = require("../domain-proof.js");
|
|
10
|
+
const { buildWellKnownProofDocument, buildEnterpriseIdentityDocument, verifyEnterpriseIdentityDocument, resolveProofTarget, writeWellKnownProofDocument, writeEnterpriseIdentityDocument } = require("../domain-proof.js");
|
|
10
11
|
const { initializeLocalPolicy, loadLocalPolicy, approvePolicyRule } = require("../local-policy.js");
|
|
12
|
+
const { createTaskExecutor } = require("../task-executor.js");
|
|
13
|
+
const { createOptimizationLoop } = require("../optimization-loop.js");
|
|
14
|
+
const crypto = require("node:crypto");
|
|
11
15
|
const { verifyPortableBundle } = require("../trust-verifier.js");
|
|
12
16
|
const { verifyReleaseManifest } = require("../release-verifier.js");
|
|
13
17
|
const { runProductionPreflight } = require("../production-preflight.js");
|
|
14
18
|
const packageMetadata = require("../package.json");
|
|
15
19
|
|
|
20
|
+
function shellQuote(value) {
|
|
21
|
+
return `'${String(value).replace(/'/g, `'"'"'`)}'`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function pinnedCliCommand(command, args = []) {
|
|
25
|
+
const executable = `npx --yes --package=@twin3-ai/agent-id@${packageMetadata.version} agent-id`;
|
|
26
|
+
return [executable, command, ...args.map(shellQuote)].join(" ");
|
|
27
|
+
}
|
|
28
|
+
|
|
16
29
|
async function post(path, body, headers = {}) {
|
|
17
30
|
const res = await fetch(endpoint + path, {
|
|
18
31
|
method: "POST",
|
|
@@ -49,6 +62,11 @@ function readFlagValue(argv, flag) {
|
|
|
49
62
|
return idx >= 0 ? argv[idx + 1] : undefined;
|
|
50
63
|
}
|
|
51
64
|
|
|
65
|
+
function readCsvFlag(argv, flag) {
|
|
66
|
+
const value = readFlagValue(argv, flag);
|
|
67
|
+
return value ? value.split(",").map((item) => item.trim()).filter(Boolean) : undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
52
70
|
async function enterpriseSessionForProject(projectRoot) {
|
|
53
71
|
const root = path.resolve(projectRoot || process.cwd());
|
|
54
72
|
const state = JSON.parse(fs.readFileSync(path.join(root, ".agent-id", "enterprise-state.json"), "utf8"));
|
|
@@ -62,7 +80,17 @@ async function enterpriseSessionForProject(projectRoot) {
|
|
|
62
80
|
credential: existing.credential,
|
|
63
81
|
identity
|
|
64
82
|
});
|
|
65
|
-
return { root, state, session };
|
|
83
|
+
return { root, state, session, identity, credential: existing.credential };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function trustedIssuerPublicKey() {
|
|
87
|
+
const response = await fetch(endpoint + "/.well-known/agentx-issuer-key.json");
|
|
88
|
+
if (!response.ok) throw new Error("enterprise issuer key unavailable");
|
|
89
|
+
const descriptor = await response.json();
|
|
90
|
+
const publicKey = String(descriptor.public_key_pem || "");
|
|
91
|
+
const actual = `sha256:${crypto.createHash("sha256").update(publicKey).digest("hex")}`;
|
|
92
|
+
if (!publicKey || actual !== TRUSTED_ISSUER_KEY_SHA256) throw new Error("enterprise issuer key pin mismatch");
|
|
93
|
+
return publicKey;
|
|
66
94
|
}
|
|
67
95
|
|
|
68
96
|
function printManagedResult(value) {
|
|
@@ -106,7 +134,8 @@ function printProductionPreflight(value) {
|
|
|
106
134
|
`API domain: ${checks.stable_api_origin ? "verified" : "not ready"}`,
|
|
107
135
|
`Release signature: ${checks.release_signature ? "verified" : "not verified"}`,
|
|
108
136
|
`npm package: ${checks.registry_integrity ? "verified" : "not ready"}`,
|
|
109
|
-
`
|
|
137
|
+
`Production binding: ${checks.production_release_binding ? "verified" : "not ready"}`,
|
|
138
|
+
`Agent surfaces: ${checks.health_endpoint && checks.agent_card && checks.install_manifest && checks.openapi ? "verified" : "not ready"}`,
|
|
110
139
|
value.ok ? `Install: ${value.install_template}` : `Blockers: ${(value.blockers || []).join(", ") || "unknown"}`,
|
|
111
140
|
"Use --json for the complete machine-readable result.",
|
|
112
141
|
];
|
|
@@ -149,86 +178,170 @@ async function releaseCheck({ printResult = true, setExitCode = true } = {}) {
|
|
|
149
178
|
return result;
|
|
150
179
|
}
|
|
151
180
|
|
|
152
|
-
|
|
153
|
-
const
|
|
154
|
-
const
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
181
|
+
function enrollmentArtifactSnapshot(projectRoot, publicRoot) {
|
|
182
|
+
const project = path.resolve(projectRoot);
|
|
183
|
+
const proof = resolveProofTarget({ projectRoot: project, publicRoot }).target;
|
|
184
|
+
const files = [
|
|
185
|
+
path.join(project, ".agent-id", "secrets", "enterprise-ed25519.pem"),
|
|
186
|
+
path.join(project, ".agent-id", "secrets", ".gitignore"),
|
|
187
|
+
path.join(project, ".agent-id", "enterprise-state.json"),
|
|
188
|
+
proof,
|
|
189
|
+
].map((target) => {
|
|
190
|
+
if (!fs.existsSync(target)) return { target, existed: false };
|
|
191
|
+
const stat = fs.lstatSync(target);
|
|
192
|
+
if (stat.isSymbolicLink() || !stat.isFile()) throw new Error(`UNSAFE_ENROLLMENT_ARTIFACT: ${target}`);
|
|
193
|
+
return { target, existed: true, content: fs.readFileSync(target), mode: stat.mode & 0o777 };
|
|
160
194
|
});
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
challenge_id: challenge.challenge_id,
|
|
172
|
-
public_key_thumbprint: identity.public_key_thumbprint,
|
|
173
|
-
domain_proof: domainProof,
|
|
195
|
+
const directoryPaths = [
|
|
196
|
+
path.join(project, ".agent-id"),
|
|
197
|
+
path.join(project, ".agent-id", "secrets"),
|
|
198
|
+
path.dirname(proof),
|
|
199
|
+
];
|
|
200
|
+
const directories = [...new Set(directoryPaths)].map((target) => {
|
|
201
|
+
if (!fs.existsSync(target)) return { target, existed: false };
|
|
202
|
+
const stat = fs.lstatSync(target);
|
|
203
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error(`UNSAFE_ENROLLMENT_DIRECTORY: ${target}`);
|
|
204
|
+
return { target, existed: true, mode: stat.mode & 0o777 };
|
|
174
205
|
});
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
206
|
+
return { project, files, directories };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function restoreEnrollmentArtifacts(snapshot) {
|
|
210
|
+
const receipt = {
|
|
211
|
+
schema: "agentx-enrollment-local-rollback-receipt-v1",
|
|
212
|
+
status: "rolled_back",
|
|
213
|
+
restored_paths: [],
|
|
214
|
+
removed_paths: [],
|
|
215
|
+
errors: [],
|
|
216
|
+
};
|
|
217
|
+
for (const item of [...snapshot.files].reverse()) {
|
|
218
|
+
try {
|
|
219
|
+
if (!item.existed) {
|
|
220
|
+
fs.rmSync(item.target, { force: true });
|
|
221
|
+
receipt.removed_paths.push(path.relative(snapshot.project, item.target));
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
fs.mkdirSync(path.dirname(item.target), { recursive: true });
|
|
225
|
+
const temporary = `${item.target}.${process.pid}.${crypto.randomUUID()}.restore`;
|
|
226
|
+
try {
|
|
227
|
+
fs.writeFileSync(temporary, item.content, { mode: item.mode, flag: "wx" });
|
|
228
|
+
fs.renameSync(temporary, item.target);
|
|
229
|
+
fs.chmodSync(item.target, item.mode);
|
|
230
|
+
} finally {
|
|
231
|
+
fs.rmSync(temporary, { force: true });
|
|
232
|
+
}
|
|
233
|
+
receipt.restored_paths.push(path.relative(snapshot.project, item.target));
|
|
234
|
+
} catch (error) {
|
|
235
|
+
receipt.errors.push({ path: path.relative(snapshot.project, item.target), code: error.code || "RESTORE_FAILED" });
|
|
236
|
+
}
|
|
184
237
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
238
|
+
for (const item of [...snapshot.directories].reverse()) {
|
|
239
|
+
try {
|
|
240
|
+
if (item.existed) fs.chmodSync(item.target, item.mode);
|
|
241
|
+
else fs.rmdirSync(item.target);
|
|
242
|
+
} catch (error) {
|
|
243
|
+
if (!item.existed && ["ENOENT", "ENOTEMPTY"].includes(error.code)) continue;
|
|
244
|
+
receipt.errors.push({ path: path.relative(snapshot.project, item.target), code: error.code || "DIRECTORY_RESTORE_FAILED" });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (receipt.errors.length) receipt.status = "rollback_incomplete";
|
|
248
|
+
return receipt;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async function createEnterpriseEnrollment({ siteUrl, projectRoot, publicRoot, environment = "production", method = "well_known", applyProof = false }) {
|
|
252
|
+
const snapshot = enrollmentArtifactSnapshot(projectRoot, publicRoot);
|
|
253
|
+
try {
|
|
254
|
+
const secretPaths = await prepareEnterpriseSecretDirectory(projectRoot);
|
|
255
|
+
const identity = generateSiteAgentIdentity({ keyPath: secretPaths.key_path });
|
|
256
|
+
const response = await post("/api/site_agents/v1/challenges", {
|
|
257
|
+
url: siteUrl,
|
|
258
|
+
public_key: identity.public_key_pem,
|
|
259
|
+
environment,
|
|
260
|
+
method,
|
|
261
|
+
});
|
|
262
|
+
const challenge = response && response.challenge;
|
|
263
|
+
if (!challenge || !challenge.challenge_id) throw new Error(response && response.error || "enterprise_enrollment_failed");
|
|
264
|
+
const domainProof = method === "well_known" ? buildWellKnownProofDocument(challenge) : null;
|
|
265
|
+
await writeEnterpriseEnrollmentState(projectRoot, {
|
|
266
|
+
status: "challenge_pending",
|
|
267
|
+
site_url: siteUrl,
|
|
268
|
+
environment,
|
|
269
|
+
agent_id: challenge.agent_id,
|
|
270
|
+
challenge_id: challenge.challenge_id,
|
|
195
271
|
public_key_thumbprint: identity.public_key_thumbprint,
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
272
|
+
domain_proof: domainProof,
|
|
273
|
+
});
|
|
274
|
+
let proofReceipt = null;
|
|
275
|
+
if (applyProof) {
|
|
276
|
+
if (method !== "well_known") throw new Error("one-line apply currently requires --method well_known");
|
|
277
|
+
proofReceipt = await writeWellKnownProofDocument({
|
|
278
|
+
challenge: domainProof,
|
|
279
|
+
projectRoot,
|
|
280
|
+
publicRoot,
|
|
281
|
+
approved: true,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
ok: true,
|
|
286
|
+
schema: "agentx-enterprise-enrollment-v1",
|
|
287
|
+
status: "challenge_pending",
|
|
288
|
+
challenge,
|
|
289
|
+
domain_proof: domainProof,
|
|
290
|
+
proof_receipt: proofReceipt,
|
|
291
|
+
identity: {
|
|
292
|
+
algorithm: identity.algorithm,
|
|
293
|
+
public_key_pem: identity.public_key_pem,
|
|
294
|
+
public_key_thumbprint: identity.public_key_thumbprint,
|
|
295
|
+
private_key_persisted_locally: true,
|
|
296
|
+
},
|
|
297
|
+
next_commands: method === "well_known" ? (applyProof ? [
|
|
298
|
+
pinnedCliCommand("enterprise-domain-verify", ["--project-root", projectRoot]),
|
|
299
|
+
] : [
|
|
300
|
+
pinnedCliCommand("enterprise-domain-proof", ["--project-root", projectRoot, "--public-root", publicRoot]),
|
|
301
|
+
pinnedCliCommand("enterprise-domain-proof", ["--project-root", projectRoot, "--public-root", publicRoot, "--apply"]),
|
|
302
|
+
pinnedCliCommand("enterprise-domain-verify", ["--project-root", projectRoot]),
|
|
303
|
+
]) : [],
|
|
304
|
+
next_actions: method === "well_known" ? ["deploy_domain_proof", "verify_domain"] : ["publish_domain_proof", "verify_domain"],
|
|
305
|
+
non_claims: [
|
|
306
|
+
"does_not_issue_shared_site_agent_key",
|
|
307
|
+
"does_not_persist_access_token",
|
|
308
|
+
"does_not_grant_mutation_authority",
|
|
309
|
+
"domain_verification_still_required",
|
|
310
|
+
],
|
|
311
|
+
};
|
|
312
|
+
} catch (error) {
|
|
313
|
+
error.enrollment_rollback_receipt = restoreEnrollmentArtifacts(snapshot);
|
|
314
|
+
throw error;
|
|
315
|
+
}
|
|
212
316
|
}
|
|
213
317
|
|
|
214
318
|
function printSignedInstallResult(value) {
|
|
215
319
|
if (process.argv.includes("--json")) return print(value);
|
|
216
320
|
const plan = value.plan || {};
|
|
217
321
|
const enrollment = value.enrollment || {};
|
|
322
|
+
const executableCommand = (enrollment.next_commands || [])[0];
|
|
218
323
|
const lines = value.dry_run ? [
|
|
219
324
|
"Agent ID signed installation plan",
|
|
220
325
|
`Site: ${plan.site_url || "unknown"}`,
|
|
221
326
|
`Framework: ${plan.framework || "unknown"}`,
|
|
222
327
|
"Identity: Ed25519, domain-bound",
|
|
223
328
|
"No files changed",
|
|
224
|
-
|
|
329
|
+
"Copy and run:",
|
|
330
|
+
value.next_command,
|
|
225
331
|
] : [
|
|
226
332
|
"Agent ID signed installation started",
|
|
227
333
|
`Site: ${plan.site_url || "unknown"}`,
|
|
228
334
|
"Signed identity: created",
|
|
229
335
|
`Domain proof: ${enrollment.proof_receipt?.relative_path || "pending"}`,
|
|
230
|
-
`Next: ${(enrollment.next_commands || ["deploy the domain proof"])[0]}`,
|
|
231
336
|
];
|
|
337
|
+
if (!value.dry_run) {
|
|
338
|
+
if (executableCommand) {
|
|
339
|
+
lines.push(`Deploy ${enrollment.proof_receipt?.relative_path || "the domain proof"}, then run:`);
|
|
340
|
+
lines.push(executableCommand);
|
|
341
|
+
} else {
|
|
342
|
+
lines.push("Next action: publish the domain proof, then verify the domain with Agent ID.");
|
|
343
|
+
}
|
|
344
|
+
}
|
|
232
345
|
lines.push("Use --json for the complete machine-readable result.");
|
|
233
346
|
console.log(lines.join("\n"));
|
|
234
347
|
}
|
|
@@ -1348,22 +1461,28 @@ async function runCli(argv) {
|
|
|
1348
1461
|
" agent-id job-evaluate <job-id>",
|
|
1349
1462
|
" agent-id register <url>",
|
|
1350
1463
|
" agent-id install-guide <url>",
|
|
1351
|
-
" agent-id install <url> [--project-root <dir>] [--public-root <dir>] [--apply]",
|
|
1464
|
+
" agent-id install <url> [--modules identity,audit,measurement,optimization,automation] [--grant identity_publish,public_read,telemetry_submit,insights_read,ai_files_write] [--project-root <dir>] [--public-root <dir>] [--apply]",
|
|
1352
1465
|
" agent-id install-resume [--project-root <dir>]",
|
|
1353
1466
|
" agent-id install-uninstall [--project-root <dir>] --approve",
|
|
1354
1467
|
" agent-id enterprise-enroll <url> [--environment production|staging] [--method well_known|dns_txt|html_meta] [--project-root <dir>]",
|
|
1355
1468
|
" agent-id enterprise-domain-proof [--project-root <dir>] [--public-root <dir>] [--apply]",
|
|
1356
1469
|
" agent-id enterprise-domain-verify [--project-root <dir>]",
|
|
1470
|
+
" agent-id enterprise-identity-publish [--project-root <dir>] [--public-root <dir>] [--apply]",
|
|
1471
|
+
" agent-id enterprise-identity-verify <agent-id-json-file> [--offline]",
|
|
1357
1472
|
" agent-id enterprise-credential-rotate [--project-root <dir>] --approve",
|
|
1358
1473
|
" agent-id enterprise-credential-revoke [--project-root <dir>] --reason <reason> --approve",
|
|
1359
1474
|
" agent-id enterprise-policy-init [--project-root <dir>]",
|
|
1360
1475
|
" agent-id enterprise-policy-approve <rule-json-file> [--project-root <dir>] --approve --approved-by <identity>",
|
|
1361
1476
|
" agent-id enterprise-status [--project-root <dir>]",
|
|
1477
|
+
" agent-id dashboard-login [--project-root <dir>] [--json]",
|
|
1362
1478
|
" agent-id enterprise-managed-enable [--project-root <dir>] [--json]",
|
|
1363
1479
|
" agent-id enterprise-managed-status [--project-root <dir>] [--json]",
|
|
1364
1480
|
" agent-id enterprise-managed-pause [--project-root <dir>] [--json]",
|
|
1365
1481
|
" agent-id enterprise-managed-resume [--project-root <dir>] [--json]",
|
|
1366
1482
|
" agent-id enterprise-managed-runs [--project-root <dir>] [--limit <1-100>] [--json]",
|
|
1483
|
+
" agent-id enterprise-task-propose <allowlisted-path> <content-file> [--project-root <dir>] [--idempotency-key <key>] [--json]",
|
|
1484
|
+
" agent-id enterprise-optimize-once [--project-root <dir>] [--approve-task <task-id>] [--json]",
|
|
1485
|
+
" agent-id enterprise-rollback-once --task-id <task-id> --project-root <dir> --approve [--json]",
|
|
1367
1486
|
" agent-id enterprise-contract",
|
|
1368
1487
|
" agent-id repository-patch-plan <root> <changes-json-file>",
|
|
1369
1488
|
" agent-id repository-patch-apply <plan-json-file> --approve",
|
|
@@ -2499,7 +2618,26 @@ async function runCli(argv) {
|
|
|
2499
2618
|
if (cmd === "install") {
|
|
2500
2619
|
if (!url) throw new Error("install requires a site URL");
|
|
2501
2620
|
const projectRoot = readFlagValue(argv, "--project-root") || process.cwd();
|
|
2502
|
-
const
|
|
2621
|
+
const modules = readCsvFlag(argv, "--modules");
|
|
2622
|
+
const grantedPermissions = readCsvFlag(argv, "--grant");
|
|
2623
|
+
const detectedPlan = await buildInstallPlan({ url, projectRoot, endpoint, modules, grantedPermissions });
|
|
2624
|
+
const publicRoot = path.resolve(readFlagValue(argv, "--public-root") || projectRoot);
|
|
2625
|
+
let realProjectRoot;
|
|
2626
|
+
let realPublicRoot;
|
|
2627
|
+
try {
|
|
2628
|
+
realProjectRoot = fs.realpathSync(path.resolve(projectRoot));
|
|
2629
|
+
realPublicRoot = fs.realpathSync(publicRoot);
|
|
2630
|
+
} catch (_error) {
|
|
2631
|
+
throw new Error("INVALID_PUBLIC_ROOT: Project root and public root must already exist.");
|
|
2632
|
+
}
|
|
2633
|
+
const relativePublicRoot = path.relative(realProjectRoot, realPublicRoot);
|
|
2634
|
+
if (relativePublicRoot.startsWith("..") || path.isAbsolute(relativePublicRoot)) {
|
|
2635
|
+
throw new Error("UNSAFE_PUBLIC_ROOT: Public root must remain inside the website project.");
|
|
2636
|
+
}
|
|
2637
|
+
const publicProofPath = path.posix.join(...relativePublicRoot.split(path.sep).filter(Boolean), ".well-known", "agent-id.json");
|
|
2638
|
+
const privateKeyPath = ".agent-id/secrets/enterprise-ed25519.pem";
|
|
2639
|
+
const managedFiles = detectedPlan.files.map(({ path: filePath, mode, sha256 }) => ({ path: filePath, mode, sha256 }));
|
|
2640
|
+
const plannedWrites = [...new Set([privateKeyPath, ".agent-id/enterprise-state.json", publicProofPath, ...managedFiles.map((item) => item.path)])];
|
|
2503
2641
|
const plan = {
|
|
2504
2642
|
schema: "agentx-signed-install-plan-v1",
|
|
2505
2643
|
site_url: detectedPlan.site_url,
|
|
@@ -2507,8 +2645,15 @@ async function runCli(argv) {
|
|
|
2507
2645
|
project_root: detectedPlan.project_root,
|
|
2508
2646
|
framework: detectedPlan.framework,
|
|
2509
2647
|
package_manager: detectedPlan.package_manager,
|
|
2648
|
+
modules: detectedPlan.modules,
|
|
2649
|
+
granted_permissions: detectedPlan.granted_permissions,
|
|
2510
2650
|
identity_mode: "ed25519_domain_bound",
|
|
2511
2651
|
approval_required: true,
|
|
2652
|
+
private_key_path: privateKeyPath,
|
|
2653
|
+
private_key_behavior: "created_locally_on_apply_never_uploaded",
|
|
2654
|
+
public_proof_path: publicProofPath,
|
|
2655
|
+
managed_files: managedFiles,
|
|
2656
|
+
planned_writes: plannedWrites,
|
|
2512
2657
|
side_effects: ["create_customer_private_key", "write_agent_id_state", "write_public_domain_proof"],
|
|
2513
2658
|
excluded_side_effects: ["package_install", "shared_site_agent_key", "customer_content_change", "deployment", "database_change", "default_branch_push"],
|
|
2514
2659
|
};
|
|
@@ -2517,13 +2662,16 @@ async function runCli(argv) {
|
|
|
2517
2662
|
ok: true,
|
|
2518
2663
|
dry_run: true,
|
|
2519
2664
|
plan,
|
|
2520
|
-
next_command:
|
|
2521
|
-
non_claims: ["
|
|
2665
|
+
next_command: pinnedCliCommand("install", [url, "--modules", detectedPlan.modules.join(","), "--grant", detectedPlan.granted_permissions.join(","), "--project-root", projectRoot, "--public-root", publicRoot, "--apply"]),
|
|
2666
|
+
non_claims: ["dry_run_does_not_register_domain", "dry_run_does_not_create_private_key", "dry_run_does_not_write_files"]
|
|
2522
2667
|
});
|
|
2523
2668
|
}
|
|
2524
|
-
|
|
2669
|
+
// All local runtime conflicts must be discovered before an identity key,
|
|
2670
|
+
// server-side challenge, or public proof is created.
|
|
2671
|
+
await preflightInstallPlan(detectedPlan);
|
|
2525
2672
|
const gate = await releaseCheck({ printResult: false, setExitCode: false });
|
|
2526
2673
|
if (!gate.ok) throw new Error(`release gate blocked: ${gate.blockers.join(", ")}`);
|
|
2674
|
+
const enrollmentSnapshot = enrollmentArtifactSnapshot(path.resolve(projectRoot), publicRoot);
|
|
2527
2675
|
const enrollment = await createEnterpriseEnrollment({
|
|
2528
2676
|
siteUrl: url,
|
|
2529
2677
|
projectRoot: path.resolve(projectRoot),
|
|
@@ -2533,12 +2681,25 @@ async function runCli(argv) {
|
|
|
2533
2681
|
applyProof: true,
|
|
2534
2682
|
});
|
|
2535
2683
|
if (!enrollment.ok) throw new Error(enrollment.error || "enterprise_enrollment_failed");
|
|
2684
|
+
// Enrollment creates the customer's key and publishes the proof, but the
|
|
2685
|
+
// runtime that polls for work is a separate set of files. Without this the
|
|
2686
|
+
// installer that writes them was never called from the CLI, so nothing was
|
|
2687
|
+
// installed, install-resume always reported not_installed, and
|
|
2688
|
+
// install-uninstall removed nothing because there was no state to find.
|
|
2689
|
+
let receipt;
|
|
2690
|
+
try {
|
|
2691
|
+
receipt = await applyInstallPlan(detectedPlan, { approved: true });
|
|
2692
|
+
} catch (error) {
|
|
2693
|
+
error.enrollment_rollback_receipt = restoreEnrollmentArtifacts(enrollmentSnapshot);
|
|
2694
|
+
throw error;
|
|
2695
|
+
}
|
|
2536
2696
|
return printSignedInstallResult({
|
|
2537
2697
|
ok: true,
|
|
2538
2698
|
dry_run: false,
|
|
2539
2699
|
plan,
|
|
2540
2700
|
release: gate,
|
|
2541
2701
|
enrollment,
|
|
2702
|
+
runtime_install: receipt,
|
|
2542
2703
|
non_claims: ["domain_verification_still_required", "shared_site_agent_key_not_used", "does_not_modify_customer_content"]
|
|
2543
2704
|
});
|
|
2544
2705
|
}
|
|
@@ -2572,12 +2733,12 @@ async function runCli(argv) {
|
|
|
2572
2733
|
schema: "agentx-domain-proof-plan-v1",
|
|
2573
2734
|
target,
|
|
2574
2735
|
document,
|
|
2575
|
-
next_command:
|
|
2736
|
+
next_command: pinnedCliCommand("enterprise-domain-proof", ["--project-root", projectRoot, "--public-root", publicRoot, "--apply"]),
|
|
2576
2737
|
non_claims: ["does_not_verify_domain", "does_not_issue_credentials", "does_not_overwrite_customer_managed_files"]
|
|
2577
2738
|
});
|
|
2578
2739
|
}
|
|
2579
2740
|
const receipt = await writeWellKnownProofDocument({ challenge: state.domain_proof, projectRoot, publicRoot, approved: true });
|
|
2580
|
-
return print({ ok: true, dry_run: false, receipt, next_command:
|
|
2741
|
+
return print({ ok: true, dry_run: false, receipt, next_command: pinnedCliCommand("enterprise-domain-verify", ["--project-root", projectRoot]) });
|
|
2581
2742
|
}
|
|
2582
2743
|
if (cmd === "enterprise-domain-verify") {
|
|
2583
2744
|
const projectRoot = path.resolve(readFlagValue(argv, "--project-root") || process.cwd());
|
|
@@ -2627,7 +2788,7 @@ async function runCli(argv) {
|
|
|
2627
2788
|
credential_path: existing.credential_path,
|
|
2628
2789
|
private_key_path: existing.key_path,
|
|
2629
2790
|
secret_storage_required: "move both customer secret files into the customer secret manager before production runtime",
|
|
2630
|
-
next_command:
|
|
2791
|
+
next_command: pinnedCliCommand("enterprise-identity-publish", ["--project-root", projectRoot, "--public-root", projectRoot])
|
|
2631
2792
|
});
|
|
2632
2793
|
}
|
|
2633
2794
|
const response = await post("/api/site_agents/v1/challenges/verify", { challenge_id: state.challenge_id });
|
|
@@ -2640,7 +2801,7 @@ async function runCli(argv) {
|
|
|
2640
2801
|
response.bootstrap_secret
|
|
2641
2802
|
);
|
|
2642
2803
|
} catch (_error) {
|
|
2643
|
-
throw new Error(`enterprise_credential_delivery_failed; request a new single-use challenge with:
|
|
2804
|
+
throw new Error(`enterprise_credential_delivery_failed; request a new single-use challenge with: ${pinnedCliCommand("enterprise-enroll", [state.site_url, "--environment", state.environment, "--method", "well_known", "--project-root", projectRoot])}`);
|
|
2644
2805
|
}
|
|
2645
2806
|
await writeEnterpriseEnrollmentState(projectRoot, {
|
|
2646
2807
|
...state,
|
|
@@ -2658,7 +2819,82 @@ async function runCli(argv) {
|
|
|
2658
2819
|
credential_path: secretReceipt.credential_path,
|
|
2659
2820
|
private_key_path: path.join(projectRoot, ".agent-id", "secrets", "enterprise-ed25519.pem"),
|
|
2660
2821
|
secret_storage_required: "move both customer secret files into the customer secret manager before production runtime",
|
|
2661
|
-
next_command:
|
|
2822
|
+
next_command: pinnedCliCommand("enterprise-identity-publish", ["--project-root", projectRoot, "--public-root", projectRoot])
|
|
2823
|
+
});
|
|
2824
|
+
}
|
|
2825
|
+
if (cmd === "enterprise-identity-publish") {
|
|
2826
|
+
const projectRoot = path.resolve(readFlagValue(argv, "--project-root") || process.cwd());
|
|
2827
|
+
const publicRoot = path.resolve(readFlagValue(argv, "--public-root") || projectRoot);
|
|
2828
|
+
const { state, identity, credential } = await enterpriseSessionForProject(projectRoot);
|
|
2829
|
+
const document = buildEnterpriseIdentityDocument({
|
|
2830
|
+
state,
|
|
2831
|
+
credential,
|
|
2832
|
+
identity,
|
|
2833
|
+
serviceOrigin: endpoint,
|
|
2834
|
+
});
|
|
2835
|
+
const verification = verifyEnterpriseIdentityDocument(document);
|
|
2836
|
+
if (!argv.includes("--apply")) {
|
|
2837
|
+
return print({
|
|
2838
|
+
ok: verification.ok,
|
|
2839
|
+
dry_run: true,
|
|
2840
|
+
schema: "agentx-site-identity-publish-plan-v1",
|
|
2841
|
+
document,
|
|
2842
|
+
verification,
|
|
2843
|
+
next_command: pinnedCliCommand("enterprise-identity-publish", ["--project-root", projectRoot, "--public-root", publicRoot, "--apply"]),
|
|
2844
|
+
non_claims: ["does_not_write_without_apply", "does_not_register_erc_8004", "does_not_grant_mutation_authority"],
|
|
2845
|
+
});
|
|
2846
|
+
}
|
|
2847
|
+
const receipt = await writeEnterpriseIdentityDocument({ document, projectRoot, publicRoot, approved: true });
|
|
2848
|
+
return print({
|
|
2849
|
+
ok: true,
|
|
2850
|
+
dry_run: false,
|
|
2851
|
+
schema: "agentx-site-identity-publish-result-v1",
|
|
2852
|
+
receipt,
|
|
2853
|
+
verification,
|
|
2854
|
+
next_actions: [
|
|
2855
|
+
`deploy ${receipt.relative_path}`,
|
|
2856
|
+
`verify ${document.credential_status.status_url}`,
|
|
2857
|
+
`verify ${document.services.passport}`,
|
|
2858
|
+
pinnedCliCommand("enterprise-policy-init", ["--project-root", projectRoot]),
|
|
2859
|
+
],
|
|
2860
|
+
});
|
|
2861
|
+
}
|
|
2862
|
+
if (cmd === "enterprise-identity-verify") {
|
|
2863
|
+
if (!url || url.startsWith("--")) throw new Error("enterprise-identity-verify requires an Agent identity JSON file");
|
|
2864
|
+
const document = JSON.parse(fs.readFileSync(path.resolve(url), "utf8"));
|
|
2865
|
+
const offline = verifyEnterpriseIdentityDocument(document);
|
|
2866
|
+
if (argv.includes("--offline") || !offline.ok) {
|
|
2867
|
+
const failures = offline.ok ? [...offline.failure_codes, "online_status_not_checked"] : offline.failure_codes;
|
|
2868
|
+
return print({
|
|
2869
|
+
...offline,
|
|
2870
|
+
ok: false,
|
|
2871
|
+
document_valid: offline.ok,
|
|
2872
|
+
trust_established: false,
|
|
2873
|
+
online_checked: false,
|
|
2874
|
+
failure_codes: [...new Set(failures)],
|
|
2875
|
+
});
|
|
2876
|
+
}
|
|
2877
|
+
const expectedStatus = `${endpoint}/agentx/id/${encodeURIComponent(document.agent_id)}/status.json`;
|
|
2878
|
+
if ((document.credential_status || {}).status_url !== expectedStatus) {
|
|
2879
|
+
return print({ ...offline, ok: false, online_checked: false, failure_codes: [...offline.failure_codes, "status_url_not_trusted_issuer"] });
|
|
2880
|
+
}
|
|
2881
|
+
const current = await get(`/agentx/id/${encodeURIComponent(document.agent_id)}/status.json`);
|
|
2882
|
+
const onlineFailures = [];
|
|
2883
|
+
if (!current || current.error) onlineFailures.push("current_status_unavailable");
|
|
2884
|
+
if (current.agent_id !== document.agent_id) onlineFailures.push("current_agent_id_mismatch");
|
|
2885
|
+
if (current.host !== document.host) onlineFailures.push("current_host_mismatch");
|
|
2886
|
+
if (!current.active || current.revoked) onlineFailures.push("current_identity_not_active");
|
|
2887
|
+
if (current.credential_status && current.credential_status !== "active") onlineFailures.push("current_credential_not_active");
|
|
2888
|
+
if (current.credential_id && current.credential_id !== document.credential_status.credential_id) onlineFailures.push("current_credential_mismatch");
|
|
2889
|
+
if (current.public_key_thumbprint !== document.verification_method.public_key_thumbprint) onlineFailures.push("current_public_key_mismatch");
|
|
2890
|
+
return print({
|
|
2891
|
+
...offline,
|
|
2892
|
+
ok: offline.ok && onlineFailures.length === 0,
|
|
2893
|
+
document_valid: offline.ok,
|
|
2894
|
+
trust_established: offline.ok && onlineFailures.length === 0,
|
|
2895
|
+
online_checked: true,
|
|
2896
|
+
current_status: current,
|
|
2897
|
+
failure_codes: [...new Set([...offline.failure_codes, ...onlineFailures])],
|
|
2662
2898
|
});
|
|
2663
2899
|
}
|
|
2664
2900
|
if (cmd === "enterprise-credential-rotate") {
|
|
@@ -2674,7 +2910,16 @@ async function runCli(argv) {
|
|
|
2674
2910
|
try { response = await session.rotateCredential(); } finally { session.clear(); }
|
|
2675
2911
|
const receipt = await replaceEnterpriseBootstrapCredential(projectRoot, response.credential, response.bootstrap_secret, existing.credential.credential_id);
|
|
2676
2912
|
await writeEnterpriseEnrollmentState(projectRoot, { ...state, status: "verified_domain", credential_id: response.credential.credential_id });
|
|
2677
|
-
return print({
|
|
2913
|
+
return print({
|
|
2914
|
+
ok: true,
|
|
2915
|
+
schema: "agentx-enterprise-credential-rotation-v1",
|
|
2916
|
+
receipt,
|
|
2917
|
+
next_actions: [
|
|
2918
|
+
"update_the_customer_secret_manager_copy",
|
|
2919
|
+
`agent-id enterprise-identity-publish --project-root ${projectRoot} --public-root <public-root> --apply`,
|
|
2920
|
+
"deploy_and_verify_the_updated_well_known_identity",
|
|
2921
|
+
],
|
|
2922
|
+
});
|
|
2678
2923
|
}
|
|
2679
2924
|
if (cmd === "enterprise-credential-revoke") {
|
|
2680
2925
|
if (!argv.includes("--approve")) throw new Error("enterprise credential revocation requires --approve");
|
|
@@ -2712,6 +2957,19 @@ async function runCli(argv) {
|
|
|
2712
2957
|
if (cmd === "enterprise-status") {
|
|
2713
2958
|
return print(await enterpriseInstallStatus(readFlagValue(argv, "--project-root") || process.cwd()));
|
|
2714
2959
|
}
|
|
2960
|
+
if (cmd === "dashboard-login") {
|
|
2961
|
+
const projectRoot = readFlagValue(argv, "--project-root") || process.cwd();
|
|
2962
|
+
const { session } = await enterpriseSessionForProject(projectRoot);
|
|
2963
|
+
try {
|
|
2964
|
+
const result = await session.createDashboardPairing();
|
|
2965
|
+
if (argv.includes("--json")) return print(result);
|
|
2966
|
+
console.log(`Dashboard: ${result.login_url}`);
|
|
2967
|
+
console.log(`Valid for ${result.expires_in} seconds and usable once.`);
|
|
2968
|
+
return;
|
|
2969
|
+
} finally {
|
|
2970
|
+
session.clear();
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2715
2973
|
if ([
|
|
2716
2974
|
"enterprise-managed-enable",
|
|
2717
2975
|
"enterprise-managed-status",
|
|
@@ -2737,6 +2995,73 @@ async function runCli(argv) {
|
|
|
2737
2995
|
session.clear();
|
|
2738
2996
|
}
|
|
2739
2997
|
}
|
|
2998
|
+
if (cmd === "enterprise-optimize-once") {
|
|
2999
|
+
const projectRoot = readFlagValue(argv, "--project-root") || process.cwd();
|
|
3000
|
+
const { root, state, session, identity, credential } = await enterpriseSessionForProject(projectRoot);
|
|
3001
|
+
const policy = loadLocalPolicy(path.join(root, ".agent-id", "enterprise-policy.json"));
|
|
3002
|
+
identity.subject = { tenant_id: credential.tenant_id, host: credential.host, environment: state.environment, agent_id: credential.agent_id };
|
|
3003
|
+
const executor = createTaskExecutor({
|
|
3004
|
+
issuerPublicKey: await trustedIssuerPublicKey(),
|
|
3005
|
+
identity,
|
|
3006
|
+
policy,
|
|
3007
|
+
connectors: { repository_patch: createRepositoryConnector({ repositoryRoot: root }) },
|
|
3008
|
+
statePath: path.join(root, ".agent-id", "executor-state.json")
|
|
3009
|
+
});
|
|
3010
|
+
const approvedTask = readFlagValue(argv, "--approve-task");
|
|
3011
|
+
try {
|
|
3012
|
+
return print(await createOptimizationLoop({ session, executor }).runOnce({ approvedTaskIds: approvedTask ? [approvedTask] : [] }));
|
|
3013
|
+
} finally {
|
|
3014
|
+
session.clear();
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
if (cmd === "enterprise-task-propose") {
|
|
3018
|
+
const projectRoot = readFlagValue(argv, "--project-root") || process.cwd();
|
|
3019
|
+
const contentFile = argv[2];
|
|
3020
|
+
if (!url || !contentFile) throw new Error("enterprise-task-propose requires <allowlisted-path> <content-file>");
|
|
3021
|
+
const policy = loadLocalPolicy(path.join(path.resolve(projectRoot), ".agent-id", "enterprise-policy.json"));
|
|
3022
|
+
const { session } = await enterpriseSessionForProject(projectRoot);
|
|
3023
|
+
try {
|
|
3024
|
+
const idempotencyKey = readFlagValue(argv, "--idempotency-key") || `cli-${crypto.randomUUID()}`;
|
|
3025
|
+
return print(await session.proposeTask({
|
|
3026
|
+
path: url,
|
|
3027
|
+
content: fs.readFileSync(path.resolve(contentFile), "utf8"),
|
|
3028
|
+
policyDigest: policy.digest,
|
|
3029
|
+
idempotencyKey,
|
|
3030
|
+
}));
|
|
3031
|
+
} finally {
|
|
3032
|
+
session.clear();
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
if (cmd === "enterprise-rollback-once") {
|
|
3036
|
+
const projectRoot = readFlagValue(argv, "--project-root") || process.cwd();
|
|
3037
|
+
const taskId = readFlagValue(argv, "--task-id");
|
|
3038
|
+
if (!taskId) throw new Error("enterprise-rollback-once requires --task-id");
|
|
3039
|
+
if (!argv.includes("--approve")) throw new Error("enterprise rollback requires --approve");
|
|
3040
|
+
const { root, state, session, identity, credential } = await enterpriseSessionForProject(projectRoot);
|
|
3041
|
+
const policy = loadLocalPolicy(path.join(root, ".agent-id", "enterprise-policy.json"));
|
|
3042
|
+
identity.subject = { tenant_id: credential.tenant_id, host: credential.host, environment: state.environment, agent_id: credential.agent_id };
|
|
3043
|
+
const executor = createTaskExecutor({
|
|
3044
|
+
issuerPublicKey: await trustedIssuerPublicKey(),
|
|
3045
|
+
identity,
|
|
3046
|
+
policy,
|
|
3047
|
+
connectors: { repository_patch: createRepositoryConnector({ repositoryRoot: root }) },
|
|
3048
|
+
statePath: path.join(root, ".agent-id", "executor-state.json")
|
|
3049
|
+
});
|
|
3050
|
+
try {
|
|
3051
|
+
const listed = await session.listTasks();
|
|
3052
|
+
let task = (listed.tasks || []).find((item) => item.task_id === taskId);
|
|
3053
|
+
if (!task) throw new Error("enterprise_task_not_found");
|
|
3054
|
+
if (["deployed", "failed"].includes(task.state)) {
|
|
3055
|
+
task = (await session.transitionTask(taskId, "rollback", task.version)).task;
|
|
3056
|
+
}
|
|
3057
|
+
if (task.state !== "rollback_pending") throw new Error(`enterprise_task_not_rollbackable:${task.state}`);
|
|
3058
|
+
const rollbackReceipt = await executor.rollback(taskId, { approved: true, task_id: taskId });
|
|
3059
|
+
const completed = await session.transitionTask(taskId, "rollback-receipt", task.version, { rollback_receipt: rollbackReceipt });
|
|
3060
|
+
return print({ ok: true, task: completed.task, rollback_receipt: rollbackReceipt });
|
|
3061
|
+
} finally {
|
|
3062
|
+
session.clear();
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
2740
3065
|
if (cmd === "repository-patch-plan") {
|
|
2741
3066
|
const root = url;
|
|
2742
3067
|
const changesFile = argv[2];
|
|
@@ -2790,7 +3115,7 @@ function runMcp() {
|
|
|
2790
3115
|
|
|
2791
3116
|
async function handleMcpLine(line) {
|
|
2792
3117
|
const msg = JSON.parse(line);
|
|
2793
|
-
if (msg.method === "initialize") return send(msg.id, { protocolVersion: "2024-11-05", serverInfo: { name: "agent-id", version: "0.
|
|
3118
|
+
if (msg.method === "initialize") return send(msg.id, { protocolVersion: "2024-11-05", serverInfo: { name: "agent-id", version: "0.3.0" } });
|
|
2794
3119
|
if (msg.method === "tools/list") {
|
|
2795
3120
|
const tools = [
|
|
2796
3121
|
{ name: "audit_url", description: "Audit a website for SEO/AEO/GEO/Agent readiness.", inputSchema: { type: "object", properties: { url: { type: "string" } }, required: ["url"] } },
|
|
@@ -3392,6 +3717,6 @@ async function handleMcpLine(line) {
|
|
|
3392
3717
|
}
|
|
3393
3718
|
|
|
3394
3719
|
runCli(process.argv.slice(2)).catch(err => {
|
|
3395
|
-
console.error(err.message);
|
|
3720
|
+
console.error(err.code && !String(err.message).includes(err.code) ? `${err.code}: ${err.message}` : err.message);
|
|
3396
3721
|
process.exit(1);
|
|
3397
3722
|
});
|