@embassys/ambassador 0.0.0 → 0.2.6
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/LICENSE +21 -0
- package/README.md +68 -2
- package/dist/agent-capabilities.d.ts +35 -0
- package/dist/agent-capabilities.js +221 -0
- package/dist/agent-capabilities.js.map +1 -0
- package/dist/ambassador-options.d.ts +9 -0
- package/dist/ambassador-options.js +31 -0
- package/dist/ambassador-options.js.map +1 -0
- package/dist/central-credential.d.ts +41 -0
- package/dist/central-credential.js +355 -0
- package/dist/central-credential.js.map +1 -0
- package/dist/central-enrollment.d.ts +31 -0
- package/dist/central-enrollment.js +278 -0
- package/dist/central-enrollment.js.map +1 -0
- package/dist/central-json.d.ts +8 -0
- package/dist/central-json.js +259 -0
- package/dist/central-json.js.map +1 -0
- package/dist/central-protected-transport.d.ts +21 -0
- package/dist/central-protected-transport.js +217 -0
- package/dist/central-protected-transport.js.map +1 -0
- package/dist/central-rest.d.ts +50 -0
- package/dist/central-rest.js +401 -0
- package/dist/central-rest.js.map +1 -0
- package/dist/cli.d.ts +26 -0
- package/dist/cli.js +131 -0
- package/dist/cli.js.map +1 -0
- package/dist/credential-store.d.ts +18 -0
- package/dist/credential-store.js +517 -0
- package/dist/credential-store.js.map +1 -0
- package/dist/delivery-profile.d.ts +43 -0
- package/dist/delivery-profile.js +253 -0
- package/dist/delivery-profile.js.map +1 -0
- package/dist/direct-delivery.d.ts +38 -0
- package/dist/direct-delivery.js +315 -0
- package/dist/direct-delivery.js.map +1 -0
- package/dist/dpop.d.ts +28 -0
- package/dist/dpop.js +119 -0
- package/dist/dpop.js.map +1 -0
- package/dist/errors.d.ts +5 -0
- package/dist/errors.js +11 -0
- package/dist/errors.js.map +1 -0
- package/dist/gateway-application.d.ts +32 -0
- package/dist/gateway-application.js +256 -0
- package/dist/gateway-application.js.map +1 -0
- package/dist/gateway-paths.d.ts +10 -0
- package/dist/gateway-paths.js +28 -0
- package/dist/gateway-paths.js.map +1 -0
- package/dist/guided-registration.d.ts +23 -0
- package/dist/guided-registration.js +117 -0
- package/dist/guided-registration.js.map +1 -0
- package/dist/identity.d.ts +20 -0
- package/dist/identity.js +54 -0
- package/dist/identity.js.map +1 -0
- package/dist/local-mcp.d.ts +28 -0
- package/dist/local-mcp.js +420 -0
- package/dist/local-mcp.js.map +1 -0
- package/dist/local-tool-result.d.ts +4 -0
- package/dist/local-tool-result.js +17 -0
- package/dist/local-tool-result.js.map +1 -0
- package/dist/mcp-contract.d.ts +10 -0
- package/dist/mcp-contract.js +63 -0
- package/dist/mcp-contract.js.map +1 -0
- package/dist/notification-journal.d.ts +24 -0
- package/dist/notification-journal.js +194 -0
- package/dist/notification-journal.js.map +1 -0
- package/dist/notification-relay.d.ts +31 -0
- package/dist/notification-relay.js +203 -0
- package/dist/notification-relay.js.map +1 -0
- package/dist/process-lock.d.ts +8 -0
- package/dist/process-lock.js +122 -0
- package/dist/process-lock.js.map +1 -0
- package/dist/sqlite-artifact.d.ts +7 -0
- package/dist/sqlite-artifact.js +121 -0
- package/dist/sqlite-artifact.js.map +1 -0
- package/dist/webhook-delivery.d.ts +23 -0
- package/dist/webhook-delivery.js +122 -0
- package/dist/webhook-delivery.js.map +1 -0
- package/docs/getting-started-claude.md +94 -0
- package/docs/getting-started-codex.md +103 -0
- package/docs/getting-started-gemini.md +87 -0
- package/docs/getting-started-hermes.md +84 -0
- package/docs/getting-started-openclaw.md +83 -0
- package/docs/live-qualification.md +174 -0
- package/package.json +48 -7
- package/index.js +0 -1
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { lstatSync } from "node:fs";
|
|
2
|
+
import { mkdir } from "node:fs/promises";
|
|
3
|
+
import { basename, dirname, resolve } from "node:path";
|
|
4
|
+
import Database from "better-sqlite3";
|
|
5
|
+
import { GatewayError } from "./errors.js";
|
|
6
|
+
import { preparePrivateSqliteArtifact } from "./sqlite-artifact.js";
|
|
7
|
+
const LOCK_HANDOFF_TIMEOUT_MS = 1_000;
|
|
8
|
+
const activeLockKeys = new Set();
|
|
9
|
+
const pendingLockPaths = new Set();
|
|
10
|
+
function errorCode(error) {
|
|
11
|
+
if (error !== null && typeof error === "object" && "code" in error) {
|
|
12
|
+
return typeof error.code === "string" ? error.code : undefined;
|
|
13
|
+
}
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
function invalidLockArtifact() {
|
|
17
|
+
return new GatewayError("lock_invalid", "The Ambassador lock artifact is invalid", 7);
|
|
18
|
+
}
|
|
19
|
+
function daemonRunning() {
|
|
20
|
+
return new GatewayError("daemon_running", "Ambassador is already running", 7);
|
|
21
|
+
}
|
|
22
|
+
function pathKey(path) {
|
|
23
|
+
return process.platform === "win32" ? path.toLowerCase() : path;
|
|
24
|
+
}
|
|
25
|
+
function filesystemLockKey(path) {
|
|
26
|
+
try {
|
|
27
|
+
const stats = lstatSync(path, { bigint: true });
|
|
28
|
+
return `artifact:${stats.dev}:${stats.ino}`;
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (errorCode(error) !== "ENOENT")
|
|
32
|
+
throw error;
|
|
33
|
+
const parentStats = lstatSync(dirname(path), { bigint: true });
|
|
34
|
+
return `new:${parentStats.dev}:${parentStats.ino}:${pathKey(basename(path))}`;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export class ProcessLock {
|
|
38
|
+
database;
|
|
39
|
+
keys;
|
|
40
|
+
released = false;
|
|
41
|
+
constructor(database, keys) {
|
|
42
|
+
this.database = database;
|
|
43
|
+
this.keys = keys;
|
|
44
|
+
}
|
|
45
|
+
static async acquire(path) {
|
|
46
|
+
const artifactPath = resolve(path);
|
|
47
|
+
const pendingPath = pathKey(artifactPath);
|
|
48
|
+
if (pendingLockPaths.has(pendingPath))
|
|
49
|
+
throw daemonRunning();
|
|
50
|
+
pendingLockPaths.add(pendingPath);
|
|
51
|
+
const keys = [];
|
|
52
|
+
try {
|
|
53
|
+
await mkdir(dirname(artifactPath), { recursive: true, mode: 0o700 });
|
|
54
|
+
const initialKey = filesystemLockKey(artifactPath);
|
|
55
|
+
if (activeLockKeys.has(initialKey))
|
|
56
|
+
throw daemonRunning();
|
|
57
|
+
activeLockKeys.add(initialKey);
|
|
58
|
+
keys.push(initialKey);
|
|
59
|
+
pendingLockPaths.delete(pendingPath);
|
|
60
|
+
const artifact = preparePrivateSqliteArtifact(artifactPath, invalidLockArtifact);
|
|
61
|
+
let database;
|
|
62
|
+
try {
|
|
63
|
+
const artifactKey = filesystemLockKey(artifactPath);
|
|
64
|
+
if (artifactKey !== initialKey) {
|
|
65
|
+
if (activeLockKeys.has(artifactKey))
|
|
66
|
+
throw daemonRunning();
|
|
67
|
+
activeLockKeys.add(artifactKey);
|
|
68
|
+
keys.push(artifactKey);
|
|
69
|
+
}
|
|
70
|
+
database = new Database(artifactPath, { timeout: LOCK_HANDOFF_TIMEOUT_MS });
|
|
71
|
+
artifact.validate();
|
|
72
|
+
// Closing another descriptor for this inode after BEGIN can release POSIX process locks.
|
|
73
|
+
artifact.releaseFile();
|
|
74
|
+
database.pragma(`busy_timeout = ${LOCK_HANDOFF_TIMEOUT_MS}`);
|
|
75
|
+
database.pragma("trusted_schema = OFF");
|
|
76
|
+
database.exec("BEGIN EXCLUSIVE");
|
|
77
|
+
artifact.validateDirectory();
|
|
78
|
+
return new ProcessLock(database, keys);
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
database?.close();
|
|
82
|
+
const code = errorCode(error);
|
|
83
|
+
if (code?.startsWith("SQLITE_BUSY"))
|
|
84
|
+
throw daemonRunning();
|
|
85
|
+
if (code === "SQLITE_NOTADB" || code === "SQLITE_FORMAT" || code === "SQLITE_CORRUPT") {
|
|
86
|
+
throw invalidLockArtifact();
|
|
87
|
+
}
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
artifact.close();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
pendingLockPaths.delete(pendingPath);
|
|
96
|
+
for (const key of keys)
|
|
97
|
+
activeLockKeys.delete(key);
|
|
98
|
+
if (errorCode(error) === "ENOENT" && keys.some((key) => key.startsWith("artifact:"))) {
|
|
99
|
+
throw daemonRunning();
|
|
100
|
+
}
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async release() {
|
|
105
|
+
if (this.released)
|
|
106
|
+
return;
|
|
107
|
+
this.released = true;
|
|
108
|
+
try {
|
|
109
|
+
this.database.exec("ROLLBACK");
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
try {
|
|
113
|
+
this.database.close();
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
for (const key of this.keys)
|
|
117
|
+
activeLockKeys.delete(key);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=process-lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-lock.js","sourceRoot":"","sources":["../src/process-lock.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface PreparedSqliteArtifact {
|
|
2
|
+
validate: () => void;
|
|
3
|
+
validateDirectory: () => void;
|
|
4
|
+
releaseFile: () => void;
|
|
5
|
+
close: () => void;
|
|
6
|
+
}
|
|
7
|
+
export declare function preparePrivateSqliteArtifact(path: string, invalidArtifact: () => Error): PreparedSqliteArtifact;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { chmodSync, closeSync, constants, fchmodSync, fstatSync, lstatSync, openSync, } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
const POSIX = process.platform !== "win32";
|
|
4
|
+
function errorCode(error) {
|
|
5
|
+
return error !== null && typeof error === "object" && "code" in error
|
|
6
|
+
? String(error.code)
|
|
7
|
+
: undefined;
|
|
8
|
+
}
|
|
9
|
+
function sameArtifact(left, right) {
|
|
10
|
+
return left.dev === right.dev && left.ino === right.ino;
|
|
11
|
+
}
|
|
12
|
+
function secureExistingFile(path, invalidArtifact) {
|
|
13
|
+
let pathStats;
|
|
14
|
+
try {
|
|
15
|
+
pathStats = lstatSync(path, { bigint: true });
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (errorCode(error) === "ENOENT")
|
|
19
|
+
return;
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
if (!pathStats.isFile() || pathStats.nlink !== 1n)
|
|
23
|
+
throw invalidArtifact();
|
|
24
|
+
if (POSIX)
|
|
25
|
+
chmodSync(path, 0o600);
|
|
26
|
+
const currentStats = lstatSync(path, { bigint: true });
|
|
27
|
+
if (!currentStats.isFile() ||
|
|
28
|
+
currentStats.nlink !== 1n ||
|
|
29
|
+
!sameArtifact(pathStats, currentStats)) {
|
|
30
|
+
throw invalidArtifact();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function preparePrivateSqliteArtifact(path, invalidArtifact) {
|
|
34
|
+
const directoryPath = dirname(path);
|
|
35
|
+
const initialDirectoryStats = lstatSync(directoryPath, { bigint: true });
|
|
36
|
+
if (!initialDirectoryStats.isDirectory())
|
|
37
|
+
throw invalidArtifact();
|
|
38
|
+
let directoryDescriptor;
|
|
39
|
+
let fileDescriptor;
|
|
40
|
+
try {
|
|
41
|
+
if (POSIX) {
|
|
42
|
+
directoryDescriptor = openSync(directoryPath, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
43
|
+
const descriptorStats = fstatSync(directoryDescriptor, { bigint: true });
|
|
44
|
+
const pathStats = lstatSync(directoryPath, { bigint: true });
|
|
45
|
+
if (!descriptorStats.isDirectory() || !pathStats.isDirectory())
|
|
46
|
+
throw invalidArtifact();
|
|
47
|
+
if (!sameArtifact(descriptorStats, pathStats))
|
|
48
|
+
throw invalidArtifact();
|
|
49
|
+
if (typeof process.getuid === "function" &&
|
|
50
|
+
descriptorStats.uid !== BigInt(process.getuid())) {
|
|
51
|
+
throw invalidArtifact();
|
|
52
|
+
}
|
|
53
|
+
fchmodSync(directoryDescriptor, 0o700);
|
|
54
|
+
}
|
|
55
|
+
for (const suffix of ["-wal", "-shm", "-journal"]) {
|
|
56
|
+
secureExistingFile(`${path}${suffix}`, invalidArtifact);
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
fileDescriptor = openSync(path, constants.O_CREAT | constants.O_EXCL | constants.O_RDWR, 0o600);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (errorCode(error) !== "EEXIST")
|
|
63
|
+
throw error;
|
|
64
|
+
const pathStats = lstatSync(path, { bigint: true });
|
|
65
|
+
if (!pathStats.isFile() || pathStats.nlink !== 1n)
|
|
66
|
+
throw invalidArtifact();
|
|
67
|
+
fileDescriptor = openSync(path, constants.O_RDWR | constants.O_NOFOLLOW);
|
|
68
|
+
}
|
|
69
|
+
const validateDirectory = () => {
|
|
70
|
+
const currentDirectoryStats = lstatSync(directoryPath, { bigint: true });
|
|
71
|
+
const expectedDirectoryStats = directoryDescriptor === undefined
|
|
72
|
+
? initialDirectoryStats
|
|
73
|
+
: fstatSync(directoryDescriptor, { bigint: true });
|
|
74
|
+
if (!currentDirectoryStats.isDirectory() ||
|
|
75
|
+
!sameArtifact(expectedDirectoryStats, currentDirectoryStats)) {
|
|
76
|
+
throw invalidArtifact();
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const validate = () => {
|
|
80
|
+
const descriptorStats = fstatSync(fileDescriptor, { bigint: true });
|
|
81
|
+
const pathStats = lstatSync(path, { bigint: true });
|
|
82
|
+
if (!descriptorStats.isFile() ||
|
|
83
|
+
!pathStats.isFile() ||
|
|
84
|
+
descriptorStats.nlink !== 1n ||
|
|
85
|
+
pathStats.nlink !== 1n ||
|
|
86
|
+
!sameArtifact(descriptorStats, pathStats)) {
|
|
87
|
+
throw invalidArtifact();
|
|
88
|
+
}
|
|
89
|
+
validateDirectory();
|
|
90
|
+
};
|
|
91
|
+
const releaseFile = () => {
|
|
92
|
+
if (fileDescriptor !== undefined) {
|
|
93
|
+
closeSync(fileDescriptor);
|
|
94
|
+
fileDescriptor = undefined;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
validate();
|
|
98
|
+
if (POSIX)
|
|
99
|
+
fchmodSync(fileDescriptor, 0o600);
|
|
100
|
+
return {
|
|
101
|
+
validate,
|
|
102
|
+
validateDirectory,
|
|
103
|
+
releaseFile,
|
|
104
|
+
close: () => {
|
|
105
|
+
releaseFile();
|
|
106
|
+
if (directoryDescriptor !== undefined) {
|
|
107
|
+
closeSync(directoryDescriptor);
|
|
108
|
+
directoryDescriptor = undefined;
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
if (fileDescriptor !== undefined)
|
|
115
|
+
closeSync(fileDescriptor);
|
|
116
|
+
if (directoryDescriptor !== undefined)
|
|
117
|
+
closeSync(directoryDescriptor);
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=sqlite-artifact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-artifact.js","sourceRoot":"","sources":["../src/sqlite-artifact.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { CentralMessage } from "./central-rest.js";
|
|
2
|
+
export type WebhookDeliveryErrorCode = "delivery_failed" | "invalid_configuration";
|
|
3
|
+
export declare class WebhookDeliveryError extends Error {
|
|
4
|
+
readonly code: WebhookDeliveryErrorCode;
|
|
5
|
+
constructor(code: WebhookDeliveryErrorCode);
|
|
6
|
+
}
|
|
7
|
+
export interface WebhookDeliveryTargetOptions {
|
|
8
|
+
readonly url: string;
|
|
9
|
+
readonly secret: string;
|
|
10
|
+
readonly fetch?: typeof fetch;
|
|
11
|
+
readonly now?: () => number;
|
|
12
|
+
readonly deadlineMs?: number;
|
|
13
|
+
readonly maximumAttempts?: number;
|
|
14
|
+
readonly retryDelayMs?: number;
|
|
15
|
+
}
|
|
16
|
+
export declare class WebhookDeliveryTarget {
|
|
17
|
+
#private;
|
|
18
|
+
constructor(options: WebhookDeliveryTargetOptions);
|
|
19
|
+
deliver(message: CentralMessage, signal: AbortSignal): Promise<{
|
|
20
|
+
readonly status: "accepted";
|
|
21
|
+
}>;
|
|
22
|
+
close(): Promise<void>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { createHmac, randomUUID } from "node:crypto";
|
|
2
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
3
|
+
import { canonicalWebhookUrl } from "./delivery-profile.js";
|
|
4
|
+
const DEFAULT_DEADLINE_MS = 10_000;
|
|
5
|
+
const DEFAULT_MAXIMUM_ATTEMPTS = 3;
|
|
6
|
+
const DEFAULT_RETRY_DELAY_MS = 250;
|
|
7
|
+
const MAX_MESSAGE_BYTES = 512 * 1024;
|
|
8
|
+
export class WebhookDeliveryError extends Error {
|
|
9
|
+
code;
|
|
10
|
+
constructor(code) {
|
|
11
|
+
super("Webhook delivery failed");
|
|
12
|
+
this.code = code;
|
|
13
|
+
this.name = "WebhookDeliveryError";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function positiveInteger(value) {
|
|
17
|
+
return Number.isSafeInteger(value) && value > 0;
|
|
18
|
+
}
|
|
19
|
+
async function cancel(response) {
|
|
20
|
+
await response.body?.cancel().catch(() => undefined);
|
|
21
|
+
}
|
|
22
|
+
export class WebhookDeliveryTarget {
|
|
23
|
+
#url;
|
|
24
|
+
#secret;
|
|
25
|
+
#authorization;
|
|
26
|
+
#fetch;
|
|
27
|
+
#now;
|
|
28
|
+
#deadlineMs;
|
|
29
|
+
#maximumAttempts;
|
|
30
|
+
#retryDelayMs;
|
|
31
|
+
#lifetime = new AbortController();
|
|
32
|
+
constructor(options) {
|
|
33
|
+
try {
|
|
34
|
+
this.#url = canonicalWebhookUrl(options.url);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
throw new WebhookDeliveryError("invalid_configuration");
|
|
38
|
+
}
|
|
39
|
+
if (!/^[\x21-\x7e]{32,256}$/u.test(options.secret)) {
|
|
40
|
+
throw new WebhookDeliveryError("invalid_configuration");
|
|
41
|
+
}
|
|
42
|
+
this.#secret = options.secret;
|
|
43
|
+
this.#authorization = `Bearer ${options.secret}`;
|
|
44
|
+
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
45
|
+
this.#now = options.now ?? Date.now;
|
|
46
|
+
this.#deadlineMs = options.deadlineMs ?? DEFAULT_DEADLINE_MS;
|
|
47
|
+
this.#maximumAttempts = options.maximumAttempts ?? DEFAULT_MAXIMUM_ATTEMPTS;
|
|
48
|
+
this.#retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
|
|
49
|
+
if (!positiveInteger(this.#deadlineMs) ||
|
|
50
|
+
!positiveInteger(this.#maximumAttempts) ||
|
|
51
|
+
!positiveInteger(this.#retryDelayMs)) {
|
|
52
|
+
throw new WebhookDeliveryError("invalid_configuration");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async deliver(message, signal) {
|
|
56
|
+
let body;
|
|
57
|
+
try {
|
|
58
|
+
body = JSON.stringify(message);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
throw new WebhookDeliveryError("delivery_failed");
|
|
62
|
+
}
|
|
63
|
+
if (Buffer.byteLength(body, "utf8") > MAX_MESSAGE_BYTES) {
|
|
64
|
+
throw new WebhookDeliveryError("delivery_failed");
|
|
65
|
+
}
|
|
66
|
+
const requestId = message.id ?? randomUUID();
|
|
67
|
+
const deadline = AbortSignal.timeout(this.#deadlineMs);
|
|
68
|
+
const requestSignal = AbortSignal.any([signal, deadline, this.#lifetime.signal]);
|
|
69
|
+
for (let attempt = 1; attempt <= this.#maximumAttempts; attempt += 1) {
|
|
70
|
+
if (requestSignal.aborted)
|
|
71
|
+
throw new WebhookDeliveryError("delivery_failed");
|
|
72
|
+
const now = this.#now();
|
|
73
|
+
if (!Number.isSafeInteger(now) || now < 0) {
|
|
74
|
+
throw new WebhookDeliveryError("invalid_configuration");
|
|
75
|
+
}
|
|
76
|
+
const timestamp = String(Math.floor(now / 1_000));
|
|
77
|
+
const signature = createHmac("sha256", this.#secret)
|
|
78
|
+
.update(timestamp, "ascii")
|
|
79
|
+
.update(".", "ascii")
|
|
80
|
+
.update(body, "utf8")
|
|
81
|
+
.digest("hex");
|
|
82
|
+
try {
|
|
83
|
+
const response = await this.#fetch(this.#url, {
|
|
84
|
+
method: "POST",
|
|
85
|
+
headers: {
|
|
86
|
+
authorization: this.#authorization,
|
|
87
|
+
"content-type": "application/json",
|
|
88
|
+
"idempotency-key": requestId,
|
|
89
|
+
"x-request-id": requestId,
|
|
90
|
+
"x-webhook-timestamp": timestamp,
|
|
91
|
+
"x-webhook-signature-v2": signature,
|
|
92
|
+
},
|
|
93
|
+
body,
|
|
94
|
+
credentials: "omit",
|
|
95
|
+
redirect: "manual",
|
|
96
|
+
signal: requestSignal,
|
|
97
|
+
});
|
|
98
|
+
const accepted = response.status >= 200 && response.status < 300;
|
|
99
|
+
await cancel(response);
|
|
100
|
+
if (accepted)
|
|
101
|
+
return { status: "accepted" };
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
if (requestSignal.aborted)
|
|
105
|
+
throw new WebhookDeliveryError("delivery_failed");
|
|
106
|
+
}
|
|
107
|
+
if (attempt < this.#maximumAttempts) {
|
|
108
|
+
try {
|
|
109
|
+
await delay(this.#retryDelayMs, undefined, { signal: requestSignal });
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
throw new WebhookDeliveryError("delivery_failed");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
throw new WebhookDeliveryError("delivery_failed");
|
|
117
|
+
}
|
|
118
|
+
async close() {
|
|
119
|
+
this.#lifetime.abort();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=webhook-delivery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-delivery.js","sourceRoot":"","sources":["../src/webhook-delivery.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Get started with Claude Code
|
|
2
|
+
|
|
3
|
+
Release target: `@embassys/ambassador@0.2.6`. The profile is source-reviewed
|
|
4
|
+
against Claude Code 2.1.257 and 2.1.258 plus
|
|
5
|
+
`@agentclientprotocol/claude-agent-acp` 0.73.0, and its exact ACP initialization
|
|
6
|
+
contract passed. Real-agent direct and webhook qualification remain open under
|
|
7
|
+
the one-release qualification exception in ADR 0015.
|
|
8
|
+
|
|
9
|
+
Ambassador enables Claude Code only for this exact contract:
|
|
10
|
+
|
|
11
|
+
| Field | Value |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| MCP `clientInfo` | `claude-code` / `2.1.257` or `2.1.258` |
|
|
14
|
+
| Delivery modes | direct and webhook |
|
|
15
|
+
| Direct command | `claude-agent-acp` |
|
|
16
|
+
| ACP adapter | `@agentclientprotocol/claude-agent-acp` / `0.73.0` |
|
|
17
|
+
| Accepted ACP `agentInfo` | `@agentclientprotocol/claude-agent-acp` / `0.73.0` |
|
|
18
|
+
| Ambassador MCP in the direct session | ACP HTTP MCP injection |
|
|
19
|
+
|
|
20
|
+
Ambassador uses the existing
|
|
21
|
+
[`agentclientprotocol/claude-agent-acp`](https://github.com/agentclientprotocol/claude-agent-acp)
|
|
22
|
+
adapter. It does not contain a Claude adapter. The reviewed release is
|
|
23
|
+
[`v0.73.0`](https://github.com/agentclientprotocol/claude-agent-acp/tree/v0.73.0).
|
|
24
|
+
Its
|
|
25
|
+
[`package.json`](https://github.com/agentclientprotocol/claude-agent-acp/blob/v0.73.0/package.json)
|
|
26
|
+
defines the `claude-agent-acp` command, pins `@agentclientprotocol/sdk` 1.4.0,
|
|
27
|
+
and pins `@anthropic-ai/claude-agent-sdk` 0.3.257. Its
|
|
28
|
+
[`acp-agent.ts`](https://github.com/agentclientprotocol/claude-agent-acp/blob/v0.73.0/src/acp-agent.ts)
|
|
29
|
+
returns the adapter identity and maps HTTP MCP session configuration into the
|
|
30
|
+
Claude Agent SDK.
|
|
31
|
+
|
|
32
|
+
The selected SDK contains Claude Code 2.1.257. The separately reviewed current
|
|
33
|
+
Claude Code package is 2.1.258. Both exact versions use the `claude-code` MCP
|
|
34
|
+
identity and map to the same fixed profile. Other Claude Code or adapter
|
|
35
|
+
versions fail closed until the registry is reviewed and updated.
|
|
36
|
+
|
|
37
|
+
## Setup
|
|
38
|
+
|
|
39
|
+
1. Install Node.js `>=24.19.0 <25` and
|
|
40
|
+
`@agentclientprotocol/claude-agent-acp` 0.73.0 through its normal package
|
|
41
|
+
installation process. Ambassador never installs or updates the adapter.
|
|
42
|
+
2. Authenticate Claude through normal Claude Code setup, or set one approved
|
|
43
|
+
adapter authentication variable outside chat: `ANTHROPIC_API_KEY`,
|
|
44
|
+
`ANTHROPIC_AUTH_TOKEN`, or `CLAUDE_CODE_OAUTH_TOKEN`.
|
|
45
|
+
3. Generate the local MCP token without putting its value in chat or a command
|
|
46
|
+
argument:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
export AMBASSADOR_LOCAL_TOKEN="$(
|
|
50
|
+
node -e "process.stdout.write(require('node:crypto').randomBytes(24).toString('hex'))"
|
|
51
|
+
)"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
4. If you may choose webhook delivery, create its secret in the same shell
|
|
55
|
+
before starting Ambassador:
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
export AMBASSADOR_WEBHOOK_SECRET="$(
|
|
59
|
+
node -e "process.stdout.write(require('node:crypto').randomBytes(24).toString('hex'))"
|
|
60
|
+
)"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
5. From the directory the direct agent may access, start the exact release and
|
|
64
|
+
keep it running in the foreground:
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
npx --yes @embassys/ambassador@0.2.6 start \
|
|
68
|
+
--local-token-env=AMBASSADOR_LOCAL_TOKEN
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
6. Configure Claude Code's MCP client to use the loopback endpoint printed by
|
|
72
|
+
Ambassador, with a bearer token read from `AMBASSADOR_LOCAL_TOKEN`. Use the
|
|
73
|
+
provider's normal MCP configuration mechanism; do not copy the token value
|
|
74
|
+
into chat.
|
|
75
|
+
7. Ask Claude Code to register with your email and optional display name. The
|
|
76
|
+
first `register_agent` call contains `email` and, if wanted, `display_name`.
|
|
77
|
+
Ambassador recognizes the Claude Code profile and asks direct versus
|
|
78
|
+
webhook, with direct as the default.
|
|
79
|
+
8. For direct mode, choose direct. The follow-up repeats the same `email` and
|
|
80
|
+
optional `display_name` and adds `delivery: {"mode":"direct"}`. Ambassador
|
|
81
|
+
starts `claude-agent-acp` and injects its authenticated HTTP MCP server into
|
|
82
|
+
the new ACP session.
|
|
83
|
+
9. For webhook mode, choose webhook and provide the HTTPS receiver URL plus
|
|
84
|
+
the environment-variable name `AMBASSADOR_WEBHOOK_SECRET`. The follow-up
|
|
85
|
+
repeats the registration fields and adds `delivery.mode`, `delivery.url`,
|
|
86
|
+
and `delivery.secret_env`; it never sends the secret value.
|
|
87
|
+
10. Enter the email verification code when Claude Code asks for it. Claude
|
|
88
|
+
Code calls `verify_email` with that `email` and six-digit `code`; the
|
|
89
|
+
central token and DPoP key stay inside Ambassador.
|
|
90
|
+
|
|
91
|
+
Ambassador does not pass `CLAUDE_CODE_EXECUTABLE` or another executable
|
|
92
|
+
override. It never receives Claude account credentials, the central token, or
|
|
93
|
+
the DPoP private key. Never put the local token or a provider credential in
|
|
94
|
+
chat or registration arguments.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Get started with Codex
|
|
2
|
+
|
|
3
|
+
Release target: `@embassys/ambassador@0.2.6`. The profile is source-reviewed
|
|
4
|
+
against Codex 0.149.0 and 0.152.1 plus
|
|
5
|
+
`@agentclientprotocol/codex-acp` 1.8.0. The exact ACP initialization contract
|
|
6
|
+
and direct live-central flow passed. Webhook qualification and a repeat of the
|
|
7
|
+
direct case on supported Node remain open under the one-release qualification
|
|
8
|
+
exception in ADR 0015.
|
|
9
|
+
|
|
10
|
+
Ambassador enables Codex only for this exact contract:
|
|
11
|
+
|
|
12
|
+
| Field | Value |
|
|
13
|
+
| --- | --- |
|
|
14
|
+
| MCP `clientInfo` | `codex-mcp-client` / `0.149.0` or `0.152.1` |
|
|
15
|
+
| Delivery modes | direct and webhook |
|
|
16
|
+
| Direct command | `codex-acp` |
|
|
17
|
+
| ACP adapter | `@agentclientprotocol/codex-acp` / `1.8.0` |
|
|
18
|
+
| Accepted ACP `agentInfo` | `@agentclientprotocol/codex-acp` / `1.8.0` |
|
|
19
|
+
| Ambassador MCP in the direct session | ACP HTTP MCP injection |
|
|
20
|
+
|
|
21
|
+
Ambassador uses the existing
|
|
22
|
+
[`agentclientprotocol/codex-acp`](https://github.com/agentclientprotocol/codex-acp)
|
|
23
|
+
adapter. It does not contain a Codex adapter. The reviewed adapter revision is
|
|
24
|
+
[`87997e2627e8fa246a49de533c612f6196c4004e`](https://github.com/agentclientprotocol/codex-acp/tree/87997e2627e8fa246a49de533c612f6196c4004e).
|
|
25
|
+
Its
|
|
26
|
+
[`package.json`](https://github.com/agentclientprotocol/codex-acp/blob/87997e2627e8fa246a49de533c612f6196c4004e/package.json)
|
|
27
|
+
defines the `codex-acp` command and exact adapter version. Its
|
|
28
|
+
[`CodexAcpServer.ts`](https://github.com/agentclientprotocol/codex-acp/blob/87997e2627e8fa246a49de533c612f6196c4004e/src/CodexAcpServer.ts)
|
|
29
|
+
returns the ACP identity, and
|
|
30
|
+
[`CodexAcpClient.ts`](https://github.com/agentclientprotocol/codex-acp/blob/87997e2627e8fa246a49de533c612f6196c4004e/src/CodexAcpClient.ts)
|
|
31
|
+
maps session MCP configuration into Codex App Server.
|
|
32
|
+
|
|
33
|
+
The Codex MCP alias comes from
|
|
34
|
+
[`rmcp_client.rs`](https://github.com/openai/codex/blob/dcfcb570b2cd0a2500b1d47a7b04a7cb1b0a0bd2/codex-rs/codex-mcp/src/rmcp_client.rs).
|
|
35
|
+
OpenAI documents App Server as the protocol for embedding Codex in another
|
|
36
|
+
product in the
|
|
37
|
+
[`Codex App Server` documentation](https://learn.chatgpt.com/docs/app-server).
|
|
38
|
+
Later Codex or adapter versions fail closed until the registry is reviewed and
|
|
39
|
+
updated.
|
|
40
|
+
|
|
41
|
+
## Setup
|
|
42
|
+
|
|
43
|
+
1. Install Node.js `>=24.19.0 <25` and
|
|
44
|
+
`@agentclientprotocol/codex-acp` 1.8.0 through its normal package
|
|
45
|
+
installation process. Ambassador never installs or updates the adapter.
|
|
46
|
+
2. Authenticate Codex through the adapter's normal ChatGPT login, or set
|
|
47
|
+
`CODEX_API_KEY` or `OPENAI_API_KEY` outside chat. The adapter owns provider
|
|
48
|
+
authentication.
|
|
49
|
+
3. Generate the local MCP token without putting its value in chat or a command
|
|
50
|
+
argument:
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
export AMBASSADOR_LOCAL_TOKEN="$(
|
|
54
|
+
node -e "process.stdout.write(require('node:crypto').randomBytes(24).toString('hex'))"
|
|
55
|
+
)"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
4. If you may choose webhook delivery, create its secret in the same shell
|
|
59
|
+
before starting Ambassador:
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
export AMBASSADOR_WEBHOOK_SECRET="$(
|
|
63
|
+
node -e "process.stdout.write(require('node:crypto').randomBytes(24).toString('hex'))"
|
|
64
|
+
)"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
5. From the directory the direct agent may access, start the exact release and
|
|
68
|
+
keep it running in the foreground:
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
npx --yes @embassys/ambassador@0.2.6 start \
|
|
72
|
+
--local-token-env=AMBASSADOR_LOCAL_TOKEN
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
6. In a shell where `AMBASSADOR_LOCAL_TOKEN` is available, configure Codex to
|
|
76
|
+
use the endpoint printed by Ambassador:
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
codex mcp add ambassador \
|
|
80
|
+
--url http://127.0.0.1:8787/mcp \
|
|
81
|
+
--bearer-token-env-var AMBASSADOR_LOCAL_TOKEN
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
7. Ask Codex to register with your email and optional display name. The first
|
|
85
|
+
`register_agent` call contains `email` and, if wanted, `display_name`.
|
|
86
|
+
Ambassador recognizes the Codex MCP profile and asks direct versus webhook,
|
|
87
|
+
with direct as the default.
|
|
88
|
+
8. For direct mode, choose direct. The follow-up repeats the same `email` and
|
|
89
|
+
optional `display_name` and adds `delivery: {"mode":"direct"}`. Ambassador
|
|
90
|
+
starts `codex-acp` and injects its authenticated HTTP MCP server into the new
|
|
91
|
+
ACP session.
|
|
92
|
+
9. For webhook mode, choose webhook and provide the HTTPS receiver URL plus
|
|
93
|
+
the environment-variable name `AMBASSADOR_WEBHOOK_SECRET`. The follow-up
|
|
94
|
+
repeats the registration fields and adds `delivery.mode`, `delivery.url`,
|
|
95
|
+
and `delivery.secret_env`; it never sends the secret value.
|
|
96
|
+
10. Enter the email verification code when Codex asks for it. Codex calls
|
|
97
|
+
`verify_email` with that `email` and six-digit `code`; the central token and
|
|
98
|
+
DPoP key stay inside Ambassador.
|
|
99
|
+
|
|
100
|
+
Ambassador does not pass `CODEX_PATH`, `CODEX_CONFIG`, or another executable or
|
|
101
|
+
session override. It never receives Codex account credentials, the central
|
|
102
|
+
token, or the DPoP private key. Never put the local token or a provider API key
|
|
103
|
+
in chat or registration arguments.
|