mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-16 09:31:37 +00:00
fix(photon): production hardening for the gRPC-native iMessage channel (#42732)
* fix(photon): override transitive CVEs in the sidecar deps `npm audit` flagged 7 high-severity transitive CVEs (protobufjs code injection GHSA-66ff-xgx4-vchm + outdated @opentelemetry OTLP exporters) pulled in via spectrum-ts -> @photon-ai/otel. npm's suggested fix downgrades spectrum-ts to a version that targets the decommissioned spectrum host, so instead pin patched versions via `overrides` (protobufjs 8.6.1, @opentelemetry/* 0.218.0) without touching spectrum-ts. `npm audit` -> 0; spectrum-ts + provider still import. * fix(photon): harden the sidecar bridge + bound the dedup cache - constant-time sidecar control-token comparison (was `!==`, timing-attackable). - cap the control-channel request body (2 MiB) so a compromised local peer can't OOM the sidecar. - wrap the inbound gRPC stream consumer in a re-subscribe loop with capped exponential backoff + jitter — if the async iterator throws/ends it would otherwise stop inbound forever (the adapter dedupes any replay). - add an unhandledRejection handler so a stray rejection logs instead of killing the process. - dedup cache (adapter) was a true bounded LRU only for expired entries; a burst of unique ids within the window grew it without limit. Evict oldest at the cap. * chore: add AUTHOR_MAP entry for PhilipAD --------- Co-authored-by: PhilipAD <philipadsouza@gmail.com>
This commit is contained in:
parent
b5421f4ba6
commit
92dfd70d6a
6 changed files with 170 additions and 177 deletions
|
|
@ -414,14 +414,19 @@ class PhotonAdapter(BasePlatformAdapter):
|
|||
|
||||
def _is_duplicate(self, msg_id: str) -> bool:
|
||||
now = time.time()
|
||||
if len(self._seen_messages) > _DEDUP_MAX_SIZE:
|
||||
cutoff = now - _DEDUP_WINDOW_SECONDS
|
||||
self._seen_messages = {
|
||||
k: v for k, v in self._seen_messages.items() if v > cutoff
|
||||
}
|
||||
if msg_id in self._seen_messages:
|
||||
return True
|
||||
self._seen_messages[msg_id] = now
|
||||
seen = self._seen_messages
|
||||
t = seen.get(msg_id)
|
||||
if t is not None and now - t < _DEDUP_WINDOW_SECONDS:
|
||||
return True # seen, unexpired
|
||||
# New or expired: record and enforce a HARD size bound (evict oldest,
|
||||
# insertion-order) so a burst of unique ids within the window can't grow
|
||||
# the dict without limit — not just the expired-only prune.
|
||||
if msg_id in seen:
|
||||
del seen[msg_id] # refresh insertion order
|
||||
seen[msg_id] = now
|
||||
if len(seen) > _DEDUP_MAX_SIZE:
|
||||
for old in list(seen.keys())[: len(seen) - _DEDUP_MAX_SIZE]:
|
||||
del seen[old]
|
||||
return False
|
||||
|
||||
async def _dispatch_inbound(self, event: Dict[str, Any]) -> None:
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
// PHOTON_SIDECAR_BIND (default 127.0.0.1)
|
||||
|
||||
import http from "node:http";
|
||||
import crypto from "node:crypto";
|
||||
import { once } from "node:events";
|
||||
|
||||
const projectId = process.env.PHOTON_PROJECT_ID;
|
||||
|
|
@ -254,32 +255,57 @@ async function normalizeEvent(space, message) {
|
|||
}
|
||||
}
|
||||
|
||||
// spectrum-ts handles in-session gRPC reconnects internally, but if the async
|
||||
// iterator itself throws or ends, this consumer would stop forever. Wrap it in
|
||||
// a re-subscribe loop with capped exponential backoff + jitter so inbound
|
||||
// always recovers (the adapter dedupes any catch-up replay).
|
||||
(async () => {
|
||||
try {
|
||||
for await (const [space, message] of app.messages) {
|
||||
// Only forward inbound messages (ignore our own outbound echoes).
|
||||
if (message && message.direction && message.direction !== "inbound") {
|
||||
continue;
|
||||
let backoff = 1000;
|
||||
for (;;) {
|
||||
try {
|
||||
for await (const [space, message] of app.messages) {
|
||||
backoff = 1000; // healthy traffic — reset
|
||||
// Only forward inbound messages (ignore our own outbound echoes).
|
||||
if (message && message.direction && message.direction !== "inbound") {
|
||||
continue;
|
||||
}
|
||||
rememberInboundSpace(space, message);
|
||||
const event = await normalizeEvent(space, message);
|
||||
if (!event) continue;
|
||||
await deliver(JSON.stringify(event));
|
||||
}
|
||||
rememberInboundSpace(space, message);
|
||||
const event = await normalizeEvent(space, message);
|
||||
if (!event) continue;
|
||||
await deliver(JSON.stringify(event));
|
||||
console.error("photon-sidecar: inbound stream ended — re-subscribing");
|
||||
} catch (e) {
|
||||
console.error(
|
||||
"photon-sidecar: inbound stream errored — restarting: " +
|
||||
(e && e.message ? e.message : String(e))
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(
|
||||
"photon-sidecar: inbound stream errored: " +
|
||||
(e && e.stack ? e.stack : String(e))
|
||||
await new Promise((r) =>
|
||||
setTimeout(r, backoff + Math.random() * backoff * 0.2)
|
||||
);
|
||||
backoff = Math.min(backoff * 2, 30000);
|
||||
}
|
||||
})();
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// HTTP control + inbound server (loopback only).
|
||||
|
||||
// Control-message bodies are tiny; cap the body so a compromised local peer
|
||||
// can't OOM the sidecar by streaming an unbounded request (defence-in-depth on
|
||||
// the loopback channel).
|
||||
const MAX_BODY_BYTES = 2 * 1024 * 1024; // 2 MiB
|
||||
async function readBody(req) {
|
||||
const chunks = [];
|
||||
for await (const chunk of req) chunks.push(chunk);
|
||||
let size = 0;
|
||||
for await (const chunk of req) {
|
||||
size += chunk.length;
|
||||
if (size > MAX_BODY_BYTES) {
|
||||
req.destroy();
|
||||
throw new Error("request body too large");
|
||||
}
|
||||
chunks.push(chunk);
|
||||
}
|
||||
const raw = Buffer.concat(chunks).toString("utf-8");
|
||||
if (!raw) return {};
|
||||
try {
|
||||
|
|
@ -386,8 +412,16 @@ async function resolveSpace(spaceId) {
|
|||
throw new Error(`unable to resolve space id ${spaceId}`);
|
||||
}
|
||||
|
||||
// Constant-time token comparison — don't leak the token via `!==` timing.
|
||||
const _tokenBuf = Buffer.from(sharedToken);
|
||||
function tokenOk(header) {
|
||||
if (typeof header !== "string") return false;
|
||||
const h = Buffer.from(header);
|
||||
return h.length === _tokenBuf.length && crypto.timingSafeEqual(h, _tokenBuf);
|
||||
}
|
||||
|
||||
const server = http.createServer(async (req, res) => {
|
||||
if (req.headers["x-hermes-sidecar-token"] !== sharedToken) {
|
||||
if (!tokenOk(req.headers["x-hermes-sidecar-token"])) {
|
||||
return unauthorized(res);
|
||||
}
|
||||
// Long-lived inbound NDJSON stream.
|
||||
|
|
@ -496,3 +530,13 @@ async function shutdown(signal) {
|
|||
|
||||
process.on("SIGINT", () => shutdown("SIGINT"));
|
||||
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
||||
|
||||
// Don't let a stray promise rejection take the process down silently — handlers
|
||||
// catch their own errors, so log and keep serving (Python supervises restart on
|
||||
// a real fatal exit).
|
||||
process.on("unhandledRejection", (reason) => {
|
||||
console.error(
|
||||
"photon-sidecar: unhandledRejection: " +
|
||||
(reason && reason.stack ? reason.stack : String(reason))
|
||||
);
|
||||
});
|
||||
|
|
|
|||
230
plugins/platforms/photon/sidecar/package-lock.json
generated
230
plugins/platforms/photon/sidecar/package-lock.json
generated
|
|
@ -119,16 +119,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/exporter-logs-otlp-http": {
|
||||
"version": "0.216.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.216.0.tgz",
|
||||
"integrity": "sha512-8SUzQY/aExKkz6Ab3vOf6gu690Xk4wHH90dGwXinejQzazn5HCIRR7yPVU/2fEuiZ73R92MU4qI3djHfYP7NJg==",
|
||||
"license": "Apache-2.0",
|
||||
"version": "0.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.218.0.tgz",
|
||||
"integrity": "sha512-Qx+4rpVHzgg89dawcWRHyt+XRXeLnhFz/qBtvggmjkcgPUdr+NAB0/u/eIPA8yAeJV0J80Vz43JZCh/XFvZFGw==",
|
||||
"dependencies": {
|
||||
"@opentelemetry/api-logs": "0.216.0",
|
||||
"@opentelemetry/api-logs": "0.218.0",
|
||||
"@opentelemetry/core": "2.7.1",
|
||||
"@opentelemetry/otlp-exporter-base": "0.216.0",
|
||||
"@opentelemetry/otlp-transformer": "0.216.0",
|
||||
"@opentelemetry/sdk-logs": "0.216.0"
|
||||
"@opentelemetry/otlp-exporter-base": "0.218.0",
|
||||
"@opentelemetry/otlp-transformer": "0.218.0",
|
||||
"@opentelemetry/sdk-logs": "0.218.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.19.0 || >=20.6.0"
|
||||
|
|
@ -137,15 +136,42 @@
|
|||
"@opentelemetry/api": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/api-logs": {
|
||||
"version": "0.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.218.0.tgz",
|
||||
"integrity": "sha512-fmEWp5kXlGEc3i/lR698Hz41DfGyN4Tbe4g7L1AxSc7fF8Xeh/FQ9Quqpa9dVA413Q1Ad43QOLzU4JoXgbFPWw==",
|
||||
"dependencies": {
|
||||
"@opentelemetry/api": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/sdk-logs": {
|
||||
"version": "0.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.218.0.tgz",
|
||||
"integrity": "sha512-QvnNdugatFTVCJXH0Mcu7GOOJSylA9j127kIezOE4YwTI4YbowRons2K4WZTv5FMS8T4q9P0NdaRHdkSmeAIag==",
|
||||
"dependencies": {
|
||||
"@opentelemetry/api-logs": "0.218.0",
|
||||
"@opentelemetry/core": "2.7.1",
|
||||
"@opentelemetry/resources": "2.7.1",
|
||||
"@opentelemetry/semantic-conventions": "^1.29.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.19.0 || >=20.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@opentelemetry/api": ">=1.4.0 <1.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/exporter-trace-otlp-http": {
|
||||
"version": "0.216.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.216.0.tgz",
|
||||
"integrity": "sha512-DhWjvj0PUPFwFnhOEivpum8sJzj6FTuyx88zff+oHVLUhfd6cLyw4AIai/F4j0PZqYZBFuMT/OTMUd9wdXnBEQ==",
|
||||
"license": "Apache-2.0",
|
||||
"version": "0.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.218.0.tgz",
|
||||
"integrity": "sha512-8dqezsmPhtKitIK/eTipZhYl9EX2/gNQ5zUMhaz3uxEURwfkNf8IPvo6yNfrzbxdtpAOybS/+h7wmIWYqFSpiw==",
|
||||
"dependencies": {
|
||||
"@opentelemetry/core": "2.7.1",
|
||||
"@opentelemetry/otlp-exporter-base": "0.216.0",
|
||||
"@opentelemetry/otlp-transformer": "0.216.0",
|
||||
"@opentelemetry/otlp-exporter-base": "0.218.0",
|
||||
"@opentelemetry/otlp-transformer": "0.218.0",
|
||||
"@opentelemetry/resources": "2.7.1",
|
||||
"@opentelemetry/sdk-trace-base": "2.7.1"
|
||||
},
|
||||
|
|
@ -157,13 +183,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/otlp-exporter-base": {
|
||||
"version": "0.216.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.216.0.tgz",
|
||||
"integrity": "sha512-sSnvb5f+FYa4mfYxj03rmmUh+aDwo3jok62dgIWUDw8ZCUPzEbgtv/YhZyKUSlKNNey7Uc5xmJgmtTLLIV6UDQ==",
|
||||
"license": "Apache-2.0",
|
||||
"version": "0.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.218.0.tgz",
|
||||
"integrity": "sha512-ZwqpkNL5W7RyGJPDZ9g06DvKp8KFTWPJPN12anpMQYSKpTSU0z3EIZuPq9vPGpS8siFyOqDYDAuCwlNO9FqgbA==",
|
||||
"dependencies": {
|
||||
"@opentelemetry/core": "2.7.1",
|
||||
"@opentelemetry/otlp-transformer": "0.216.0"
|
||||
"@opentelemetry/otlp-transformer": "0.218.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.19.0 || >=20.6.0"
|
||||
|
|
@ -173,18 +198,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/otlp-transformer": {
|
||||
"version": "0.216.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.216.0.tgz",
|
||||
"integrity": "sha512-g4Rb6sAsxQAo11eDjixfKxelruBsQFdJ8Wo23FCj7D6OXbidgXMu2xaRSYs4RdlomzAXSJuc86RcS3xmE8A6uA==",
|
||||
"license": "Apache-2.0",
|
||||
"version": "0.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.218.0.tgz",
|
||||
"integrity": "sha512-CFaKH87WAzjuJ4awowTTLzUvMfaRfiOFG5+qm5S5ncyalRtN4ecQ+YmuANJSCrVPuvZFEkUgKhBPBndxi3rHsQ==",
|
||||
"dependencies": {
|
||||
"@opentelemetry/api-logs": "0.216.0",
|
||||
"@opentelemetry/api-logs": "0.218.0",
|
||||
"@opentelemetry/core": "2.7.1",
|
||||
"@opentelemetry/resources": "2.7.1",
|
||||
"@opentelemetry/sdk-logs": "0.216.0",
|
||||
"@opentelemetry/sdk-logs": "0.218.0",
|
||||
"@opentelemetry/sdk-metrics": "2.7.1",
|
||||
"@opentelemetry/sdk-trace-base": "2.7.1",
|
||||
"protobufjs": "8.0.1"
|
||||
"@opentelemetry/sdk-trace-base": "2.7.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.19.0 || >=20.6.0"
|
||||
|
|
@ -193,28 +216,32 @@
|
|||
"@opentelemetry/api": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/otlp-transformer/node_modules/protobufjs": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.0.1.tgz",
|
||||
"integrity": "sha512-NWWCCscLjs+cOKF/s/XVNFRW7Yih0fdH+9brffR5NZCy8k42yRdl5KlWKMVXuI1vfCoy4o1z80XR/W/QUb3V3w==",
|
||||
"hasInstallScript": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/api-logs": {
|
||||
"version": "0.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.218.0.tgz",
|
||||
"integrity": "sha512-fmEWp5kXlGEc3i/lR698Hz41DfGyN4Tbe4g7L1AxSc7fF8Xeh/FQ9Quqpa9dVA413Q1Ad43QOLzU4JoXgbFPWw==",
|
||||
"dependencies": {
|
||||
"@protobufjs/aspromise": "^1.1.2",
|
||||
"@protobufjs/base64": "^1.1.2",
|
||||
"@protobufjs/codegen": "^2.0.4",
|
||||
"@protobufjs/eventemitter": "^1.1.0",
|
||||
"@protobufjs/fetch": "^1.1.0",
|
||||
"@protobufjs/float": "^1.0.2",
|
||||
"@protobufjs/inquire": "^1.1.0",
|
||||
"@protobufjs/path": "^1.1.2",
|
||||
"@protobufjs/pool": "^1.1.0",
|
||||
"@protobufjs/utf8": "^1.1.0",
|
||||
"@types/node": ">=13.7.0",
|
||||
"long": "^5.0.0"
|
||||
"@opentelemetry/api": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/sdk-logs": {
|
||||
"version": "0.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.218.0.tgz",
|
||||
"integrity": "sha512-QvnNdugatFTVCJXH0Mcu7GOOJSylA9j127kIezOE4YwTI4YbowRons2K4WZTv5FMS8T4q9P0NdaRHdkSmeAIag==",
|
||||
"dependencies": {
|
||||
"@opentelemetry/api-logs": "0.218.0",
|
||||
"@opentelemetry/core": "2.7.1",
|
||||
"@opentelemetry/resources": "2.7.1",
|
||||
"@opentelemetry/semantic-conventions": "^1.29.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.19.0 || >=20.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@opentelemetry/api": ">=1.4.0 <1.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/resources": {
|
||||
|
|
@ -255,7 +282,6 @@
|
|||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.7.1.tgz",
|
||||
"integrity": "sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@opentelemetry/core": "2.7.1",
|
||||
"@opentelemetry/resources": "2.7.1"
|
||||
|
|
@ -402,97 +428,12 @@
|
|||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@photon-ai/whatsapp-business/node_modules/protobufjs": {
|
||||
"version": "8.4.2",
|
||||
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.4.2.tgz",
|
||||
"integrity": "sha512-64rfNzkWOZAIazXzpBFPWq6F9up6gMvTzjE2oWIzApx2N/dqVUEE7+bCn2+40780dFVtKOUab8QfxJ6KJDWbqA==",
|
||||
"hasInstallScript": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"long": "^5.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@protobufjs/aspromise": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
||||
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/base64": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
|
||||
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/codegen": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz",
|
||||
"integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/eventemitter": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz",
|
||||
"integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/fetch": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz",
|
||||
"integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@protobufjs/aspromise": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@protobufjs/float": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
|
||||
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/inquire": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz",
|
||||
"integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/path": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
|
||||
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/pool": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
|
||||
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/utf8": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz",
|
||||
"integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@repeaterjs/repeater": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@repeaterjs/repeater/-/repeater-3.0.6.tgz",
|
||||
"integrity": "sha512-Javneu5lsuhwNCryN+pXH93VPQ8g0dBX7wItHFgYiwQmzE1sVdg5tWHiOgHywzL2W21XQopa7IwIEnNbmeUJYA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "25.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz",
|
||||
"integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": ">=7.24.0 <7.24.7"
|
||||
}
|
||||
},
|
||||
"node_modules/abort-controller-x": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/abort-controller-x/-/abort-controller-x-0.5.0.tgz",
|
||||
|
|
@ -1306,23 +1247,10 @@
|
|||
}
|
||||
},
|
||||
"node_modules/protobufjs": {
|
||||
"version": "7.6.1",
|
||||
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.1.tgz",
|
||||
"integrity": "sha512-4K0myLaWL5EteuSAro91EGFgcfVgxb64Jx+7oDAY6GOkXD4M69yuSEljNcInGVCA5sOPxmZ/EqDLj2x0Q0+Ygg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"version": "8.6.1",
|
||||
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.6.1.tgz",
|
||||
"integrity": "sha512-s4qQPr4pU0W95iYnUInh95skjIg+3aM2sakYsw60QYanU+qWRDY2zQxOAQV6zU7ROJpSNDG9B+VSmk4dqdWWSA==",
|
||||
"dependencies": {
|
||||
"@protobufjs/aspromise": "^1.1.2",
|
||||
"@protobufjs/base64": "^1.1.2",
|
||||
"@protobufjs/codegen": "^2.0.5",
|
||||
"@protobufjs/eventemitter": "^1.1.1",
|
||||
"@protobufjs/fetch": "^1.1.1",
|
||||
"@protobufjs/float": "^1.0.2",
|
||||
"@protobufjs/inquire": "^1.1.2",
|
||||
"@protobufjs/path": "^1.1.2",
|
||||
"@protobufjs/pool": "^1.1.0",
|
||||
"@protobufjs/utf8": "^1.1.1",
|
||||
"@types/node": ">=13.7.0",
|
||||
"long": "^5.3.2"
|
||||
},
|
||||
"engines": {
|
||||
|
|
@ -1651,12 +1579,6 @@
|
|||
"node": ">=20.18.1"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.24.6",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
|
||||
"integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
|
|
|
|||
|
|
@ -13,5 +13,12 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"spectrum-ts": "^1.18.0"
|
||||
},
|
||||
"overrides": {
|
||||
"protobufjs": "8.6.1",
|
||||
"@opentelemetry/otlp-transformer": "0.218.0",
|
||||
"@opentelemetry/otlp-exporter-base": "0.218.0",
|
||||
"@opentelemetry/exporter-trace-otlp-http": "0.218.0",
|
||||
"@opentelemetry/exporter-logs-otlp-http": "0.218.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ ACP_REGISTRY_MANIFEST = REPO_ROOT / "acp_registry" / "agent.json"
|
|||
|
||||
# Auto-extracted from noreply emails + manual overrides
|
||||
AUTHOR_MAP = {
|
||||
"philipadsouza@gmail.com": "PhilipAD",
|
||||
"zhuhaoyu0909@icloud.com": "underthestars-zhy",
|
||||
"raysun12142006@gmail.com": "yanxue06",
|
||||
"alberto.regalado@ymail.com": "ARegalado1",
|
||||
|
|
|
|||
|
|
@ -292,6 +292,20 @@ def test_is_duplicate_window(monkeypatch: pytest.MonkeyPatch) -> None:
|
|||
assert adapter._is_duplicate("id-1") is True # still dup
|
||||
|
||||
|
||||
def test_is_duplicate_hard_size_bound(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
# A burst of unique ids within the window must not grow the dedup map past
|
||||
# its bound — evict oldest (LRU), not only expired entries.
|
||||
import plugins.platforms.photon.adapter as ad
|
||||
|
||||
monkeypatch.setattr(ad, "_DEDUP_MAX_SIZE", 5)
|
||||
adapter = _make_adapter(monkeypatch)
|
||||
for i in range(100):
|
||||
adapter._is_duplicate(f"id-{i}")
|
||||
assert len(adapter._seen_messages) <= 5
|
||||
assert adapter._is_duplicate("id-99") is True # recent still deduped
|
||||
assert adapter._is_duplicate("id-0") is False # oldest evicted
|
||||
|
||||
|
||||
def test_check_requirements_without_node(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
# If no node binary on PATH the adapter should refuse to start.
|
||||
from plugins.platforms.photon import adapter as adapter_mod
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue