@robota-sdk/agent-transport 3.0.0-beta.78 → 3.0.0-beta.81
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/README.md +84 -26
- package/dist/browser/client.d.ts +2 -0
- package/dist/browser/client.js +1 -0
- package/dist/browser/index.d.ts +520 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +2 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/message-decoders-BB8n-H2X.d.ts +252 -0
- package/dist/browser/message-decoders-BB8n-H2X.d.ts.map +1 -0
- package/dist/browser/message-decoders-CDmDoZPR.js +2 -0
- package/dist/browser/message-decoders-CDmDoZPR.js.map +1 -0
- package/dist/node/index.cjs +1 -1
- package/dist/node/index.d.cts +766 -0
- package/dist/node/index.d.cts.map +1 -0
- package/dist/node/index.d.ts +765 -27
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +1 -1
- package/dist/node/index.js.map +1 -1
- package/dist/node/node.cjs +1 -0
- package/dist/node/node.d.cts +109 -0
- package/dist/node/node.d.cts.map +1 -0
- package/dist/node/node.d.ts +109 -0
- package/dist/node/node.d.ts.map +1 -0
- package/dist/node/node.js +2 -0
- package/dist/node/node.js.map +1 -0
- package/package.json +68 -47
- package/src/__tests__/access-token-verifier.test.ts +672 -0
- package/src/__tests__/admission.test.ts +130 -0
- package/src/__tests__/channel-frames.test.ts +214 -0
- package/src/__tests__/driver-attribution-capability.test.ts +83 -0
- package/src/__tests__/handoff-chunking.test.ts +215 -0
- package/src/__tests__/handoff-manifest.test.ts +52 -0
- package/src/__tests__/message-decoders.test.ts +249 -0
- package/src/__tests__/outbound-delivery.test.ts +558 -0
- package/src/__tests__/peer-message-ledger.test.ts +200 -0
- package/src/__tests__/personal-usage-report.test.ts +193 -0
- package/src/__tests__/resume-buffer.test.ts +93 -0
- package/src/__tests__/session-event-delivery.test.ts +101 -0
- package/src/__tests__/session-message-handler-e5.test.ts +99 -0
- package/src/__tests__/session-message-handler.test.ts +590 -0
- package/src/__tests__/session-resume-bridge.test.ts +406 -0
- package/src/__tests__/submit-prompt-shape.test.ts +78 -0
- package/src/__tests__/usage-report-carrier.test.ts +98 -0
- package/src/__tests__/ws-broadcast-events.test.ts +84 -0
- package/src/background-messages.ts +169 -0
- package/src/channel-frames.ts +182 -0
- package/src/client.ts +9 -0
- package/src/handoff-chunking.ts +235 -0
- package/src/index.ts +112 -3
- package/src/message-decoders.ts +229 -0
- package/src/message-parser.ts +33 -0
- package/src/node/access-token-verifier.ts +363 -0
- package/src/node/admission.ts +153 -0
- package/src/node/handoff-manifest.ts +77 -0
- package/src/node/index.ts +10 -0
- package/src/outbound-delivery.ts +231 -0
- package/src/peer-message-ledger.ts +193 -0
- package/src/protocol-session.ts +28 -0
- package/src/resume-buffer.ts +123 -0
- package/src/session-events.ts +198 -0
- package/src/session-message-handler.ts +282 -0
- package/src/session-query-messages.ts +42 -0
- package/src/session-resume-bridge.ts +252 -0
- package/src/usage-messages.ts +195 -0
- package/src/wire-messages.ts +165 -0
- package/dist/node/headless/index.cjs +0 -1
- package/dist/node/headless/index.d.ts +0 -2
- package/dist/node/headless/index.js +0 -1
- package/dist/node/headless-DG_MEbf0.js +0 -19
- package/dist/node/headless-DG_MEbf0.js.map +0 -1
- package/dist/node/headless-xq77JBq0.cjs +0 -18
- package/dist/node/index-CrJ2Lhn3.d.ts +0 -96
- package/dist/node/index-CrJ2Lhn3.d.ts.map +0 -1
- package/dist/node/index-JIvzrtaA.d.ts +0 -52
- package/dist/node/index-JIvzrtaA.d.ts.map +0 -1
- package/dist/node/index-JTD_xtEQ.d.ts +0 -96
- package/dist/node/index-JTD_xtEQ.d.ts.map +0 -1
- package/dist/node/index-y_HBOfzO.d.ts +0 -52
- package/dist/node/index-y_HBOfzO.d.ts.map +0 -1
- package/dist/node/programmatic/index.cjs +0 -1
- package/dist/node/programmatic/index.d.ts +0 -2
- package/dist/node/programmatic/index.js +0 -1
- package/dist/node/programmatic-6wLf68qJ.cjs +0 -1
- package/dist/node/programmatic-70dxIXtS.js +0 -2
- package/dist/node/programmatic-70dxIXtS.js.map +0 -1
- package/dist/node/testing/index.cjs +0 -1
- package/dist/node/testing/index.d.ts +0 -2
- package/dist/node/testing/index.js +0 -1
- package/src/__tests__/programmatic/programmatic-ask-user.test.ts +0 -32
- package/src/__tests__/programmatic/programmatic-driver.test.ts +0 -107
- package/src/__tests__/programmatic/programmatic-flows.test.ts +0 -140
- package/src/__tests__/transport-registry.stop-all.test.ts +0 -53
- package/src/headless/HeadlessInteractionChannel.ts +0 -129
- package/src/headless/__tests__/headless-channel-options.test.ts +0 -106
- package/src/headless/__tests__/headless-goal-runner.test.ts +0 -108
- package/src/headless/__tests__/headless-provider-failure.integration.test.ts +0 -143
- package/src/headless/__tests__/headless-runner-initialization.test.ts +0 -45
- package/src/headless/__tests__/headless-runner.test.ts +0 -508
- package/src/headless/__tests__/headless-skill-activation.integration.test.ts +0 -430
- package/src/headless/__tests__/headless-transport.test.ts +0 -267
- package/src/headless/cli-input.ts +0 -50
- package/src/headless/headless-runner.ts +0 -230
- package/src/headless/headless-stream-json.ts +0 -148
- package/src/headless/headless-transport.ts +0 -43
- package/src/headless/index.ts +0 -8
- package/src/headless/print-terminal.ts +0 -57
- package/src/programmatic/ProgrammaticInteractionChannel.ts +0 -82
- package/src/programmatic/createProgrammaticAgent.ts +0 -71
- package/src/programmatic/index.ts +0 -5
- package/src/testing/index.ts +0 -10
- package/src/transport-registry.ts +0 -103
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { IEgressDeps } from "@robota-sdk/agent-core/node";
|
|
2
|
+
import { IInteractiveSessionRecord } from "@robota-sdk/agent-interface-session";
|
|
3
|
+
import { IAccessTokenVerifier, IAccessTokenVerifierConfig, ITransportAdmission, ITransportAdmissionConfig } from "@robota-sdk/agent-interface-transport";
|
|
4
|
+
import { IHandoffIntegrity } from "@robota-sdk/agent-interface-session-mobility";
|
|
5
|
+
//#region src/node/admission.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Mint a per-launch credential.
|
|
8
|
+
*
|
|
9
|
+
* Throws rather than returning a short or empty string if entropy is unavailable, so a transport
|
|
10
|
+
* built on it fails to construct instead of binding with a guessable credential.
|
|
11
|
+
*/
|
|
12
|
+
declare function mintTransportToken(): string;
|
|
13
|
+
/**
|
|
14
|
+
* Resolve the admission decision for one transport.
|
|
15
|
+
*
|
|
16
|
+
* IDEMPOTENT: it accepts an already-resolved `ITransportAdmission` and returns it unchanged, so
|
|
17
|
+
* `resolve(resolve(x))` is `resolve(x)`. That is not a convenience — it is what lets a caller
|
|
18
|
+
* holding either shape just call this, instead of deciding which shape it holds first.
|
|
19
|
+
*
|
|
20
|
+
* The HTTP route used to make that decision with `'token' in options.admission`, and review showed
|
|
21
|
+
* it cannot work: BOTH interfaces declare a `token`, so a config is only distinguishable from a
|
|
22
|
+
* resolution by the VALUE. `{ token: '' }` — documented as "mint a fresh one" — was read as
|
|
23
|
+
* pre-resolved and installed the empty string as the required credential, which a peer sending an
|
|
24
|
+
* empty bearer would then match. Making this function idempotent deletes that discriminator
|
|
25
|
+
* instead of repairing it.
|
|
26
|
+
*
|
|
27
|
+
* @throws if `open` is set without a reason — an unexplained open transport is the state this
|
|
28
|
+
* module exists to remove, so it is refused at construction rather than recorded and shipped.
|
|
29
|
+
* @throws if a token and `open: true` are asked for together. The two are contradictory, and the
|
|
30
|
+
* silent precedence this used to give (token wins) is the safe DIRECTION but the wrong ANSWER: a
|
|
31
|
+
* caller that wrote both does not know what it is asking for, and one of the two things it believes
|
|
32
|
+
* is false. `WebRtcTransport` already refused the analogous `secret` + `open` pair in its own
|
|
33
|
+
* constructor; a seam whose whole claim is "one place to read and one place to change" cannot leave
|
|
34
|
+
* that protection to whichever transport happens to have implemented it.
|
|
35
|
+
*/
|
|
36
|
+
declare function resolveAdmission(config?: ITransportAdmissionConfig | ITransportAdmission): ITransportAdmission;
|
|
37
|
+
/**
|
|
38
|
+
* Whether a presented credential matches the required one.
|
|
39
|
+
*
|
|
40
|
+
* Compared with `timingSafeEqual` over the raw bytes, so the answer takes the same time whatever the
|
|
41
|
+
* first differing character is. The length check before it is unavoidable — `timingSafeEqual` throws
|
|
42
|
+
* on unequal lengths — and it does leak one bit: whether the presented credential is the right
|
|
43
|
+
* LENGTH.
|
|
44
|
+
*
|
|
45
|
+
* For a minted token that costs nothing: the length is fixed at 64 hex characters and published in
|
|
46
|
+
* this file. A host that supplies its own token of some other length leaks that length instead, and
|
|
47
|
+
* review was right to point out that the earlier wording ("fixed and public") quietly assumed the
|
|
48
|
+
* minted case for every case. Length is not the secret in either — but the claim now says which
|
|
49
|
+
* situation it is describing.
|
|
50
|
+
*
|
|
51
|
+
* A missing credential is a mismatch, not a special case: a peer that sends nothing and a peer that
|
|
52
|
+
* sends the wrong thing are equally unadmitted, and giving them different answers would tell an
|
|
53
|
+
* attacker which half they got right.
|
|
54
|
+
*/
|
|
55
|
+
declare function credentialMatches(expected: string, presented: string | undefined): boolean;
|
|
56
|
+
declare function bearerCredential(header: string | undefined | null): string | undefined;
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/node/handoff-manifest.d.ts
|
|
59
|
+
/**
|
|
60
|
+
* Serialize a record and describe what was serialized.
|
|
61
|
+
*
|
|
62
|
+
* The string is returned alongside the integrity data because the digest is only meaningful for
|
|
63
|
+
* these exact bytes. A caller that re-serialized the record before sending could produce a
|
|
64
|
+
* different key order and a digest mismatch on a payload that is perfectly correct — a corruption
|
|
65
|
+
* report caused by the corruption check.
|
|
66
|
+
*/
|
|
67
|
+
declare function sealHandoffRecord(record: IInteractiveSessionRecord): {
|
|
68
|
+
serialized: string;
|
|
69
|
+
integrity: IHandoffIntegrity;
|
|
70
|
+
};
|
|
71
|
+
/** Why a received payload was not accepted. */
|
|
72
|
+
type TIntegrityFailure = 'truncated' | 'digest-mismatch';
|
|
73
|
+
interface IIntegrityVerdict {
|
|
74
|
+
readonly intact: boolean;
|
|
75
|
+
readonly failure?: TIntegrityFailure;
|
|
76
|
+
/** What the manifest promised and what arrived, so a report can say more than "it failed". */
|
|
77
|
+
readonly expectedBytes?: number;
|
|
78
|
+
readonly actualBytes?: number;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Check a received payload against the manifest's integrity metadata.
|
|
82
|
+
*
|
|
83
|
+
* Nothing is parsed here. Parsing a payload to check it is well-formed would mean building the
|
|
84
|
+
* object graph before knowing whether the bytes are the ones that were sent, and a destination that
|
|
85
|
+
* has already parsed a corrupt payload has already spent the effort this check exists to avoid.
|
|
86
|
+
*/
|
|
87
|
+
declare function verifyHandoffPayload(serialized: string, integrity: IHandoffIntegrity): IIntegrityVerdict;
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region src/node/access-token-verifier.d.ts
|
|
90
|
+
/** What the verifier reaches outward through. Injected in tests; defaults to the real network. */
|
|
91
|
+
interface IAccessTokenVerifierDeps {
|
|
92
|
+
readonly fetch?: IEgressDeps['fetch'];
|
|
93
|
+
readonly lookup?: IEgressDeps['lookup'];
|
|
94
|
+
/** Wall-clock milliseconds since the epoch, for `exp`/`nbf`. Defaults to `Date.now`. */
|
|
95
|
+
readonly now?: () => number;
|
|
96
|
+
/** Monotonic milliseconds, for cache age and refetch intervals. Defaults to `performance.now`. */
|
|
97
|
+
readonly monotonicNow?: () => number;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Build a verifier for one issuer and one resource.
|
|
101
|
+
*
|
|
102
|
+
* @throws when the configuration cannot describe a safe verifier: a non-`https` issuer, no
|
|
103
|
+
* algorithm or an unsupported one, no required scope, or no allowlisted subject or client. The
|
|
104
|
+
* last is the single-tenant rule: without it any holder of a token from the issuer is admitted.
|
|
105
|
+
*/
|
|
106
|
+
declare function createAccessTokenVerifier(config: IAccessTokenVerifierConfig, deps?: IAccessTokenVerifierDeps): IAccessTokenVerifier;
|
|
107
|
+
//#endregion
|
|
108
|
+
export { type IAccessTokenVerifierDeps, type IIntegrityVerdict, type TIntegrityFailure, bearerCredential, createAccessTokenVerifier, credentialMatches, mintTransportToken, resolveAdmission, sealHandoffRecord, verifyHandoffPayload };
|
|
109
|
+
//# sourceMappingURL=node.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.d.ts","names":[],"sources":["../../src/node/admission.ts","../../src/node/handoff-manifest.ts","../../src/node/access-token-verifier.ts"],"mappings":";;;;;;;;;;;iBA4BgB;;;;;;;;;;;;;;;;;;;;;;;;iBA+BA,iBACd,SAAQ,4BAA4B,sBACnC;;;;;;;;;;;;;;;;;;;iBAoDa,kBAAkB,kBAAkB;iBA8BpC,iBAAiB;;;;;;;;;;;iBCpHjB,kBAAkB,QAAQ;EACxC;EACA,WAAW;;;KAcD;UAEK;WACN;WACA,UAAU;;WAEV;WACA;;;;;;;;;iBAUK,qBACd,oBACA,WAAW,oBACV;;;;UCCc;WACN,QAAQ;WACR,SAAS;;WAET;;WAEA;;;;;;;;;iBAwBK,0BACd,QAAQ,4BACR,OAAM,2BACL"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{createHash as e,randomBytes as t,timingSafeEqual as n}from"node:crypto";import{decodeProtectedHeader as r,importJWK as i,jwtVerify as a}from"jose";import{fetchWithEgressPolicy as o}from"@robota-sdk/agent-core/node";function s(){let e=t(32).toString(`hex`);if(e.length!==64)throw Error(`transport admission: could not mint a credential of the required length`);return e}function c(e={}){let t=`open`in e&&e.open===!0,n=e.token!==void 0&&e.token!==null&&e.token!==``;if(n&&t)throw Error("transport admission: a token and `open: true` are contradictory — one requires a credential and the other requires none. Asking for both means one of the two beliefs behind the call is wrong; say which is intended.");if(n)return{token:e.token};if(e.token===null||t){let t=e.openReason?.trim();if(!t)throw Error("transport admission: an open transport requires `openReason`. An open transport is a decision, and a decision nobody wrote down is indistinguishable from an oversight.");return{token:null,openReason:t}}return{token:s()}}function l(e,t){if(t===void 0)return!1;let r=Buffer.from(e,`utf8`),i=Buffer.from(t,`utf8`);return r.length===i.length&&n(r,i)}function u(e){if(!e||e.slice(0,6).toLowerCase()!==`bearer`)return;let t=e.slice(6);if(t===``||t[0]!==` `&&t[0]!==` `)return;let n=t.replace(/^[ \t]+/,``);return n===``?void 0:n}function d(t){let n=JSON.stringify(t),r=Buffer.from(n,`utf8`);return{serialized:n,integrity:{digest:e(`sha256`).update(r).digest(`base64url`),byteLength:r.byteLength}}}function f(t,n){let r=Buffer.from(t,`utf8`);return r.byteLength===n.byteLength?e(`sha256`).update(r).digest(`base64url`)===n.digest?{intact:!0}:{intact:!1,failure:`digest-mismatch`}:{intact:!1,failure:`truncated`,expectedBytes:n.byteLength,actualBytes:r.byteLength}}const p=10*6e4,m=`at+jwt`,h=[`RS256`,`ES256`,`EdDSA`],g={RS256:{kty:`RSA`},ES256:{kty:`EC`,crv:`P-256`},EdDSA:{kty:`OKP`,crv:`Ed25519`}};var _=class{refusal;constructor(e){this.refusal=e}};function v(e,t={}){let n=y(e),s=t.now??Date.now,c=t.monotonicNow??(()=>performance.now()),l={fetch:t.fetch,lookup:t.lookup},u=n.hostname.replace(/^\[|\]$/g,``).toLowerCase(),d={keys:void 0,jwksUri:void 0,fetchedAt:void 0,lastAttemptAt:void 0,inflight:void 0},f=new Set(e.allowedSubjects??[]),h=new Set(e.allowedClients??[]);async function g(e){if(new URL(e).protocol!==`https:`)return;let t=await o(e,{headers:{accept:`application/json`},timeoutMs:1e4,maxResponseBytes:65536},{allowedHosts:[u],maxRedirects:0},l);if(!(!t.ok||t.status!==200))return JSON.parse(new TextDecoder().decode(t.body))}async function v(){for(let t of b(n)){let n=await g(t).catch(()=>void 0);if(!T(n))continue;if(n.issuer!==e.issuer)return;let r=n.jwks_uri;return typeof r!=`string`||!URL.canParse(r)?void 0:new URL(r).protocol===`https:`?r:void 0}}function E(){return d.fetchedAt===void 0?1/0:c()-d.fetchedAt}async function D(){d.lastAttemptAt=c();try{if(E()>=p&&(d.jwksUri=void 0),d.jwksUri??=await v(),d.jwksUri===void 0)return;let e=x(await g(d.jwksUri));if(e===void 0)return;d.keys=e,d.fetchedAt=c()}catch{}}async function O(){if(d.inflight!==void 0)return d.inflight;if(!(d.lastAttemptAt!==void 0&&c()-d.lastAttemptAt<3e4))return d.inflight=D().finally(()=>{d.inflight=void 0}),d.inflight}async function k(){return E()>=p&&await O(),E()>=36e5&&(d.keys=void 0,d.jwksUri=void 0,d.fetchedAt=void 0),d.keys}async function A(e,t){let n=await k();if(n===void 0)throw new _(`keys-unavailable`);if(e===void 0){if(n.length>1)throw new _(`ambiguous-key`);let e=n[0];if(e===void 0)throw new _(`unknown-key`);if(!S(e,t))throw new _(`key-mismatch`);return e}let r=n.filter(t=>t.kid===e);if(r.length===0&&(await O(),n=d.keys??[],r=n.filter(t=>t.kid===e)),r.length===0)throw new _(`unknown-key`);let i=r.find(e=>S(e,t));if(i===void 0)throw new _(`key-mismatch`);return i}async function j(t){if(typeof t!=`string`||t.length===0)throw new _(`malformed`);if(t.length>8192)throw new _(`oversize`);let n;try{n=r(t)}catch{throw new _(`malformed`)}if(typeof n.typ!=`string`||w(n.typ)!==m)throw new _(`wrong-type`);let o=e.algorithms.find(e=>e===n.alg);if(o===void 0)throw new _(`unsupported-algorithm`);if(n.kid!==void 0&&typeof n.kid!=`string`)throw new _(`malformed`);let c=await A(n.kid,o),l;try{l=await i(c,o)}catch{throw new _(`key-mismatch`)}let u;try{({payload:u}=await a(t,l,{issuer:e.issuer,audience:e.resource,algorithms:[o],typ:m,clockTolerance:60,currentDate:new Date(s()),requiredClaims:[`exp`,`sub`,`client_id`]}))}catch(e){throw new _(C(e))}let d=typeof u.scope==`string`?u.scope.split(` `):[];if(!e.requiredScopes.every(e=>d.includes(e)))throw new _(`missing-scope`);let p=u.sub,g=u.client_id;if(!(typeof p==`string`&&f.has(p)||typeof g==`string`&&h.has(g)))throw new _(`principal-not-allowed`);return{admitted:!0}}return{async verify(e){try{return await j(e)}catch(e){return{admitted:!1,refusal:e instanceof _?e.refusal:`malformed`}}}}}function y(e){if(!URL.canParse(e.issuer))throw Error(`access-token verifier: issuer is not a URL`);let t=new URL(e.issuer);if(t.protocol!==`https:`||t.search!==``||t.hash!==``)throw Error(`access-token verifier: issuer must be an https URL with no query or fragment`);if(!URL.canParse(e.resource))throw Error(`access-token verifier: resource is not a URL`);if(e.algorithms.length===0||!e.algorithms.every(e=>h.includes(e)))throw Error(`access-token verifier: algorithms must be a non-empty subset of RS256, ES256, EdDSA`);if(e.requiredScopes.length===0)throw Error(`access-token verifier: at least one required scope is needed`);if((e.allowedSubjects?.length??0)+(e.allowedClients?.length??0)===0)throw Error(`access-token verifier: name at least one allowed subject or client — this host is single-tenant, and issuer + audience + scope alone admit anyone the issuer serves`);return t}function b(e){let t=e.pathname===`/`?``:e.pathname.replace(/\/$/,``);return[`${e.origin}/.well-known/oauth-authorization-server${t}`,`${e.origin}${t}/.well-known/openid-configuration`]}function x(e){if(!(!T(e)||!Array.isArray(e.keys)||e.keys.length>32))return e.keys.filter(e=>T(e)&&typeof e.kty==`string`)}function S(e,t){let n=g[t];return e.kty!==n.kty||n.crv!==void 0&&e.crv!==n.crv||e.alg!==void 0&&e.alg!==t||e.use!==void 0&&e.use!==`sig`||e.key_ops!==void 0&&!e.key_ops.includes(`verify`)?!1:e.d===void 0}function C(e){let t=T(e)?e.code:void 0,n=T(e)?e.claim:void 0;switch(t){case`ERR_JWT_EXPIRED`:return`expired`;case`ERR_JWS_SIGNATURE_VERIFICATION_FAILED`:return`bad-signature`;case`ERR_JOSE_ALG_NOT_ALLOWED`:return`unsupported-algorithm`;case`ERR_JWT_CLAIM_VALIDATION_FAILED`:return n===`iss`?`wrong-issuer`:n===`aud`?`wrong-audience`:n===`nbf`?`not-yet-valid`:n===`typ`?`wrong-type`:`malformed`;default:return`malformed`}}function w(e){let t=e.toLowerCase();return t.startsWith(`application/`)?t.slice(12):t}function T(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}export{u as bearerCredential,v as createAccessTokenVerifier,l as credentialMatches,s as mintTransportToken,c as resolveAdmission,d as sealHandoffRecord,f as verifyHandoffPayload};
|
|
2
|
+
//# sourceMappingURL=node.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.js","names":[],"sources":["../../src/node/admission.ts","../../src/node/handoff-manifest.ts","../../src/node/access-token-verifier.ts"],"sourcesContent":["/**\n * SEC-008 — the machinery behind the admission decision.\n *\n * The SHAPE of the decision is a contract and lives in `@robota-sdk/agent-interface-transport`; an\n * interface package is inert by rule, and minting and comparing a credential need `node:crypto`. So\n * the types are declared there and produced here, in the package every transport already shares.\n *\n * Splitting them is not bookkeeping. Putting the crypto in the contract package would have given\n * every consumer of those types a runtime dependency edge on a Node builtin — the same defect\n * CORE-028 removed from another package, reintroduced one package over.\n */\n\nimport { randomBytes, timingSafeEqual } from 'node:crypto';\n\nimport type {\n ITransportAdmission,\n ITransportAdmissionConfig,\n} from '@robota-sdk/agent-interface-transport';\n\n/** Bytes of entropy per minted token. 32 bytes = 256 bits, hex-encoded to 64 characters. */\nconst TOKEN_BYTES = 32;\n\n/**\n * Mint a per-launch credential.\n *\n * Throws rather than returning a short or empty string if entropy is unavailable, so a transport\n * built on it fails to construct instead of binding with a guessable credential.\n */\nexport function mintTransportToken(): string {\n const token = randomBytes(TOKEN_BYTES).toString('hex');\n if (token.length !== TOKEN_BYTES * 2) {\n throw new Error('transport admission: could not mint a credential of the required length');\n }\n return token;\n}\n\n/**\n * Resolve the admission decision for one transport.\n *\n * IDEMPOTENT: it accepts an already-resolved `ITransportAdmission` and returns it unchanged, so\n * `resolve(resolve(x))` is `resolve(x)`. That is not a convenience — it is what lets a caller\n * holding either shape just call this, instead of deciding which shape it holds first.\n *\n * The HTTP route used to make that decision with `'token' in options.admission`, and review showed\n * it cannot work: BOTH interfaces declare a `token`, so a config is only distinguishable from a\n * resolution by the VALUE. `{ token: '' }` — documented as \"mint a fresh one\" — was read as\n * pre-resolved and installed the empty string as the required credential, which a peer sending an\n * empty bearer would then match. Making this function idempotent deletes that discriminator\n * instead of repairing it.\n *\n * @throws if `open` is set without a reason — an unexplained open transport is the state this\n * module exists to remove, so it is refused at construction rather than recorded and shipped.\n * @throws if a token and `open: true` are asked for together. The two are contradictory, and the\n * silent precedence this used to give (token wins) is the safe DIRECTION but the wrong ANSWER: a\n * caller that wrote both does not know what it is asking for, and one of the two things it believes\n * is false. `WebRtcTransport` already refused the analogous `secret` + `open` pair in its own\n * constructor; a seam whose whole claim is \"one place to read and one place to change\" cannot leave\n * that protection to whichever transport happens to have implemented it.\n */\nexport function resolveAdmission(\n config: ITransportAdmissionConfig | ITransportAdmission = {},\n): ITransportAdmission {\n const requestedOpen = 'open' in config && config.open === true;\n const hasToken = config.token !== undefined && config.token !== null && config.token !== '';\n\n if (hasToken && requestedOpen) {\n throw new Error(\n 'transport admission: a token and `open: true` are contradictory — one requires a credential ' +\n 'and the other requires none. Asking for both means one of the two beliefs behind the call ' +\n 'is wrong; say which is intended.',\n );\n }\n\n if (hasToken) {\n return { token: config.token as string };\n }\n\n // `token: null` is the RESOLVED open state, not an absent token — the one value that tells an\n // already-resolved admission apart from a config. It still has to carry its reason: an\n // admission reaching this function without one was either built by hand or corrupted, and both\n // are the unexplained-open state the reason exists to prevent.\n if (config.token === null || requestedOpen) {\n const reason = config.openReason?.trim();\n if (!reason) {\n throw new Error(\n 'transport admission: an open transport requires `openReason`. An open transport is a ' +\n 'decision, and a decision nobody wrote down is indistinguishable from an oversight.',\n );\n }\n return { token: null, openReason: reason };\n }\n\n return { token: mintTransportToken() };\n}\n\n/**\n * Whether a presented credential matches the required one.\n *\n * Compared with `timingSafeEqual` over the raw bytes, so the answer takes the same time whatever the\n * first differing character is. The length check before it is unavoidable — `timingSafeEqual` throws\n * on unequal lengths — and it does leak one bit: whether the presented credential is the right\n * LENGTH.\n *\n * For a minted token that costs nothing: the length is fixed at 64 hex characters and published in\n * this file. A host that supplies its own token of some other length leaks that length instead, and\n * review was right to point out that the earlier wording (\"fixed and public\") quietly assumed the\n * minted case for every case. Length is not the secret in either — but the claim now says which\n * situation it is describing.\n *\n * A missing credential is a mismatch, not a special case: a peer that sends nothing and a peer that\n * sends the wrong thing are equally unadmitted, and giving them different answers would tell an\n * attacker which half they got right.\n */\nexport function credentialMatches(expected: string, presented: string | undefined): boolean {\n if (presented === undefined) return false;\n const a = Buffer.from(expected, 'utf8');\n const b = Buffer.from(presented, 'utf8');\n if (a.length !== b.length) return false;\n return timingSafeEqual(a, b);\n}\n\n/**\n * Extract a bearer credential from an `Authorization` header value.\n *\n * Returns `undefined` for anything that is not a bearer token, including a header that merely\n * starts with the right word — a peer sending `Bearerness xyz` is sending something else.\n *\n * The SCHEME is matched case-insensitively, per RFC 7235: `auth-scheme` is a token and tokens are\n * compared without regard to case, so `bearer …` and `BEARER …` are the same request. Review found\n * the exact-case match; it failed safe, refusing a conformant client with a 401 it could not act on,\n * which is the kind of refusal that gets read as a server bug and worked around rather than fixed.\n * The credential itself is untouched — it is not a token in that sense and its case is significant.\n *\n * Separator: one or more spaces or tabs, not exactly one space. That is the same clause's `BWS`.\n *\n * PARSED, not matched. `/^Bearer[ \\t]+(.+)$/i` said the same thing and CodeQL was right about it: two\n * adjacent variable-length runs over attacker-controlled input is a polynomial backtrack, and this\n * input arrives on an unauthenticated request — the one place a slow path is worth reaching for.\n * Reading the prefix, then trimming, has no backtracking to exploit and says the same thing more\n * plainly.\n */\nconst BEARER_PREFIX = 'bearer';\n\nexport function bearerCredential(header: string | undefined | null): string | undefined {\n if (!header) return undefined;\n if (header.slice(0, BEARER_PREFIX.length).toLowerCase() !== BEARER_PREFIX) return undefined;\n const rest = header.slice(BEARER_PREFIX.length);\n // A separator is REQUIRED: `Bearerness xyz` starts with the word and is a different scheme, so\n // `rest` must begin with the whitespace that ends the scheme token.\n if (rest === '' || (rest[0] !== ' ' && rest[0] !== '\\t')) return undefined;\n const credential = rest.replace(/^[ \\t]+/, '');\n return credential === '' ? undefined : credential;\n}\n","/**\n * HANDOFF-001 (#1811): what actually gets built and sent, and how the destination knows it arrived\n * whole.\n *\n * Session mobility decides whether a source can offer a session and what its inventory says.\n * This Node-only module seals and verifies the exact payload bytes.\n *\n * ## Integrity is checked in a specific order\n *\n * Length first, then digest. A truncated transfer is the common failure and the cheap one to\n * detect; hashing a short buffer to discover it was short wastes the work and, worse, reports it as\n * a digest mismatch — which reads as corruption or tampering rather than a dropped connection.\n */\n\nimport { createHash } from 'node:crypto';\n\nimport type { IInteractiveSessionRecord } from '@robota-sdk/agent-interface-session';\nimport type { IHandoffIntegrity } from '@robota-sdk/agent-interface-session-mobility';\n\n/**\n * Serialize a record and describe what was serialized.\n *\n * The string is returned alongside the integrity data because the digest is only meaningful for\n * these exact bytes. A caller that re-serialized the record before sending could produce a\n * different key order and a digest mismatch on a payload that is perfectly correct — a corruption\n * report caused by the corruption check.\n */\nexport function sealHandoffRecord(record: IInteractiveSessionRecord): {\n serialized: string;\n integrity: IHandoffIntegrity;\n} {\n const serialized = JSON.stringify(record);\n const bytes = Buffer.from(serialized, 'utf8');\n return {\n serialized,\n integrity: {\n digest: createHash('sha256').update(bytes).digest('base64url'),\n byteLength: bytes.byteLength,\n },\n };\n}\n\n/** Why a received payload was not accepted. */\nexport type TIntegrityFailure = 'truncated' | 'digest-mismatch';\n\nexport interface IIntegrityVerdict {\n readonly intact: boolean;\n readonly failure?: TIntegrityFailure;\n /** What the manifest promised and what arrived, so a report can say more than \"it failed\". */\n readonly expectedBytes?: number;\n readonly actualBytes?: number;\n}\n\n/**\n * Check a received payload against the manifest's integrity metadata.\n *\n * Nothing is parsed here. Parsing a payload to check it is well-formed would mean building the\n * object graph before knowing whether the bytes are the ones that were sent, and a destination that\n * has already parsed a corrupt payload has already spent the effort this check exists to avoid.\n */\nexport function verifyHandoffPayload(\n serialized: string,\n integrity: IHandoffIntegrity,\n): IIntegrityVerdict {\n const bytes = Buffer.from(serialized, 'utf8');\n if (bytes.byteLength !== integrity.byteLength) {\n return {\n intact: false,\n failure: 'truncated',\n expectedBytes: integrity.byteLength,\n actualBytes: bytes.byteLength,\n };\n }\n const digest = createHash('sha256').update(bytes).digest('base64url');\n if (digest !== integrity.digest) return { intact: false, failure: 'digest-mismatch' };\n return { intact: true };\n}\n","/**\n * The access-token verifier behind `IAccessTokenVerifier`.\n *\n * The cryptography is `jose`'s, not ours: signature verification, claim validation and key import\n * are the parts most often got subtly wrong by hand. What this module adds is the policy around\n * them — which tokens are even offered to `jose`, which key a token may be checked against, and\n * where that key may be fetched from.\n *\n * Keys are found through the issuer's own metadata (RFC 8414, then OpenID discovery) and fetched\n * through the shared egress boundary with private-address reach granted to the issuer's host only,\n * so an internal authorization server works without opening the resource server's egress to every\n * internal address. Every fetch is `https`, byte-bounded, and refuses redirects: a key set is the\n * root of trust for every admission after it, and a redirect is a second origin nobody configured.\n *\n * The key set is cached, and refetched — metadata included — when a token names a `kid` the cache\n * does not hold or when the cache has outlived its maximum age, at most once per interval, so a\n * stream of tokens with invented `kid`s cannot turn this into a request amplifier against the\n * issuer. The age bound is what makes a key the issuer withdrew (rotation after a compromise) stop\n * admitting without anyone presenting a new `kid`. An outage is tolerated only while the cached keys\n * are younger than a hard limit; past it they are dropped and every token is refused, because an\n * outage is not a reason to keep trusting keys nobody has confirmed for an hour.\n */\n\nimport { decodeProtectedHeader, importJWK, jwtVerify } from 'jose';\n\nimport { fetchWithEgressPolicy } from '@robota-sdk/agent-core/node';\nimport type { IEgressDeps } from '@robota-sdk/agent-core/node';\nimport type {\n IAccessTokenVerifier,\n IAccessTokenVerifierConfig,\n TAccessTokenAdmission,\n TAccessTokenAlgorithm,\n TAccessTokenRefusal,\n} from '@robota-sdk/agent-interface-transport';\nimport type { JWK, JWTPayload } from 'jose';\n\n/** Longest token offered to the parser. Real access tokens are a few hundred bytes to a few KiB. */\nconst MAX_TOKEN_LENGTH = 8192;\n/** Byte cap on a metadata document or key set. */\nconst MAX_DOCUMENT_BYTES = 64 * 1024;\n/** Keys accepted from one key set. */\nconst MAX_KEYS = 32;\n/** Deadline for one metadata or key-set exchange. */\nconst FETCH_TIMEOUT_MS = 10_000;\n/** Minimum gap between two key-set fetches, whatever triggered them. */\nconst MIN_REFETCH_INTERVAL_MS = 30_000;\n/** Age after which the key set and metadata are refetched before the next verification. */\nconst MAX_KEY_AGE_MS = 10 * 60_000;\n/** Age past which cached keys are dropped if they could not be refreshed. */\nconst HARD_KEY_AGE_MS = 60 * 60_000;\n/** Clock skew tolerated on `exp` and `nbf`, in seconds. */\nconst CLOCK_SKEW_SECONDS = 60;\nconst ACCESS_TOKEN_TYP = 'at+jwt';\n\nconst SUPPORTED_ALGORITHMS: readonly TAccessTokenAlgorithm[] = ['RS256', 'ES256', 'EdDSA'];\n\n/** The key shape each algorithm requires. A key that disagrees is never offered to `jose`. */\nconst KEY_SHAPE: Readonly<Record<TAccessTokenAlgorithm, { kty: string; crv?: string }>> = {\n RS256: { kty: 'RSA' },\n ES256: { kty: 'EC', crv: 'P-256' },\n EdDSA: { kty: 'OKP', crv: 'Ed25519' },\n};\n\n/** What the verifier reaches outward through. Injected in tests; defaults to the real network. */\nexport interface IAccessTokenVerifierDeps {\n readonly fetch?: IEgressDeps['fetch'];\n readonly lookup?: IEgressDeps['lookup'];\n /** Wall-clock milliseconds since the epoch, for `exp`/`nbf`. Defaults to `Date.now`. */\n readonly now?: () => number;\n /** Monotonic milliseconds, for cache age and refetch intervals. Defaults to `performance.now`. */\n readonly monotonicNow?: () => number;\n}\n\ninterface IKeyCache {\n keys: readonly JWK[] | undefined;\n jwksUri: string | undefined;\n /** Monotonic time of the last successful key-set fetch. */\n fetchedAt: number | undefined;\n /** Monotonic time of the last attempt, successful or not; the rate bound runs from here. */\n lastAttemptAt: number | undefined;\n inflight: Promise<void> | undefined;\n}\n\nclass Refused {\n constructor(readonly refusal: TAccessTokenRefusal) {}\n}\n\n/**\n * Build a verifier for one issuer and one resource.\n *\n * @throws when the configuration cannot describe a safe verifier: a non-`https` issuer, no\n * algorithm or an unsupported one, no required scope, or no allowlisted subject or client. The\n * last is the single-tenant rule: without it any holder of a token from the issuer is admitted.\n */\nexport function createAccessTokenVerifier(\n config: IAccessTokenVerifierConfig,\n deps: IAccessTokenVerifierDeps = {},\n): IAccessTokenVerifier {\n const issuerUrl = validateConfig(config);\n const now = deps.now ?? Date.now;\n const monotonicNow = deps.monotonicNow ?? (() => performance.now());\n const egressDeps: IEgressDeps = { fetch: deps.fetch, lookup: deps.lookup };\n const issuerHost = issuerUrl.hostname.replace(/^\\[|\\]$/g, '').toLowerCase();\n const cache: IKeyCache = {\n keys: undefined,\n jwksUri: undefined,\n fetchedAt: undefined,\n lastAttemptAt: undefined,\n inflight: undefined,\n };\n const allowedSubjects = new Set(config.allowedSubjects ?? []);\n const allowedClients = new Set(config.allowedClients ?? []);\n\n async function fetchJson(url: string): Promise<unknown> {\n if (new URL(url).protocol !== 'https:') return undefined;\n const result = await fetchWithEgressPolicy(\n url,\n {\n headers: { accept: 'application/json' },\n timeoutMs: FETCH_TIMEOUT_MS,\n maxResponseBytes: MAX_DOCUMENT_BYTES,\n },\n { allowedHosts: [issuerHost], maxRedirects: 0 },\n egressDeps,\n );\n if (!result.ok || result.status !== 200) return undefined;\n return JSON.parse(new TextDecoder().decode(result.body)) as unknown;\n }\n\n async function discoverJwksUri(): Promise<string | undefined> {\n for (const url of metadataUrls(issuerUrl)) {\n const metadata = await fetchJson(url).catch(() => undefined);\n if (!isRecord(metadata)) continue;\n // RFC 8414 §3.3: metadata naming another issuer must not be used — and a server that\n // answered with the wrong issuer is not one to ask a second question of.\n if (metadata.issuer !== config.issuer) return undefined;\n const jwksUri = metadata.jwks_uri;\n if (typeof jwksUri !== 'string' || !URL.canParse(jwksUri)) return undefined;\n return new URL(jwksUri).protocol === 'https:' ? jwksUri : undefined;\n }\n return undefined;\n }\n\n function keyAge(): number {\n return cache.fetchedAt === undefined ? Infinity : monotonicNow() - cache.fetchedAt;\n }\n\n async function fetchKeys(): Promise<void> {\n cache.lastAttemptAt = monotonicNow();\n try {\n // Metadata is rediscovered with the key set once the cache is old: a `jwks_uri` is part of\n // what the issuer can rotate.\n if (keyAge() >= MAX_KEY_AGE_MS) cache.jwksUri = undefined;\n cache.jwksUri ??= await discoverJwksUri();\n if (cache.jwksUri === undefined) return;\n const keys = parseKeySet(await fetchJson(cache.jwksUri));\n if (keys === undefined) return;\n cache.keys = keys;\n cache.fetchedAt = monotonicNow();\n } catch {\n // allow-fallback: a failed fetch leaves the cache as it was, and `currentKeys` drops it once\n // it passes the hard age limit — the fail-closed direction.\n }\n }\n\n /** Refetch unless one was attempted within the interval. Concurrent callers share one fetch. */\n async function refreshKeys(): Promise<void> {\n if (cache.inflight !== undefined) return cache.inflight;\n if (\n cache.lastAttemptAt !== undefined &&\n monotonicNow() - cache.lastAttemptAt < MIN_REFETCH_INTERVAL_MS\n ) {\n return;\n }\n cache.inflight = fetchKeys().finally(() => {\n cache.inflight = undefined;\n });\n return cache.inflight;\n }\n\n /** The cached keys, refreshed first when old; `undefined` when none may be trusted. */\n async function currentKeys(): Promise<readonly JWK[] | undefined> {\n if (keyAge() >= MAX_KEY_AGE_MS) await refreshKeys();\n if (keyAge() >= HARD_KEY_AGE_MS) {\n cache.keys = undefined;\n cache.jwksUri = undefined;\n cache.fetchedAt = undefined;\n }\n return cache.keys;\n }\n\n async function selectKey(kid: string | undefined, alg: TAccessTokenAlgorithm): Promise<JWK> {\n let keys = await currentKeys();\n if (keys === undefined) throw new Refused('keys-unavailable');\n if (kid === undefined) {\n if (keys.length > 1) throw new Refused('ambiguous-key');\n const only = keys[0];\n if (only === undefined) throw new Refused('unknown-key');\n if (!keyAgrees(only, alg)) throw new Refused('key-mismatch');\n return only;\n }\n let named = keys.filter((key) => key.kid === kid);\n if (named.length === 0) {\n await refreshKeys();\n keys = cache.keys ?? [];\n named = keys.filter((key) => key.kid === kid);\n }\n if (named.length === 0) throw new Refused('unknown-key');\n // Two keys may share a `kid` across algorithms; the one checked is the one that fits the token.\n const fitting = named.find((key) => keyAgrees(key, alg));\n if (fitting === undefined) throw new Refused('key-mismatch');\n return fitting;\n }\n\n async function check(token: string): Promise<TAccessTokenAdmission> {\n if (typeof token !== 'string' || token.length === 0) throw new Refused('malformed');\n if (token.length > MAX_TOKEN_LENGTH) throw new Refused('oversize');\n\n let header: ReturnType<typeof decodeProtectedHeader>;\n try {\n header = decodeProtectedHeader(token);\n } catch {\n throw new Refused('malformed');\n }\n if (typeof header.typ !== 'string' || normalizeTyp(header.typ) !== ACCESS_TOKEN_TYP) {\n throw new Refused('wrong-type');\n }\n const alg = config.algorithms.find((allowed) => allowed === header.alg);\n if (alg === undefined) throw new Refused('unsupported-algorithm');\n if (header.kid !== undefined && typeof header.kid !== 'string') throw new Refused('malformed');\n\n const jwk = await selectKey(header.kid, alg);\n let key: Awaited<ReturnType<typeof importJWK>>;\n try {\n key = await importJWK(jwk, alg);\n } catch {\n throw new Refused('key-mismatch');\n }\n\n let payload: JWTPayload;\n try {\n ({ payload } = await jwtVerify(token, key, {\n issuer: config.issuer,\n audience: config.resource,\n algorithms: [alg],\n typ: ACCESS_TOKEN_TYP,\n clockTolerance: CLOCK_SKEW_SECONDS,\n currentDate: new Date(now()),\n requiredClaims: ['exp', 'sub', 'client_id'],\n }));\n } catch (error) {\n throw new Refused(refusalFor(error));\n }\n\n const granted = typeof payload.scope === 'string' ? payload.scope.split(' ') : [];\n if (!config.requiredScopes.every((scope) => granted.includes(scope))) {\n throw new Refused('missing-scope');\n }\n const subject = payload.sub;\n const client = payload.client_id;\n const allowed =\n (typeof subject === 'string' && allowedSubjects.has(subject)) ||\n (typeof client === 'string' && allowedClients.has(client));\n if (!allowed) throw new Refused('principal-not-allowed');\n return { admitted: true };\n }\n\n return {\n async verify(token: string): Promise<TAccessTokenAdmission> {\n try {\n return await check(token);\n } catch (error) {\n // Only the reason word leaves: never the token, a claim, or an underlying error message.\n return { admitted: false, refusal: error instanceof Refused ? error.refusal : 'malformed' };\n }\n },\n };\n}\n\nfunction validateConfig(config: IAccessTokenVerifierConfig): URL {\n if (!URL.canParse(config.issuer)) throw new Error('access-token verifier: issuer is not a URL');\n const issuerUrl = new URL(config.issuer);\n if (issuerUrl.protocol !== 'https:' || issuerUrl.search !== '' || issuerUrl.hash !== '') {\n throw new Error('access-token verifier: issuer must be an https URL with no query or fragment');\n }\n if (!URL.canParse(config.resource)) {\n throw new Error('access-token verifier: resource is not a URL');\n }\n if (\n config.algorithms.length === 0 ||\n !config.algorithms.every((alg) => SUPPORTED_ALGORITHMS.includes(alg))\n ) {\n throw new Error(\n 'access-token verifier: algorithms must be a non-empty subset of RS256, ES256, EdDSA',\n );\n }\n if (config.requiredScopes.length === 0) {\n throw new Error('access-token verifier: at least one required scope is needed');\n }\n const principals = (config.allowedSubjects?.length ?? 0) + (config.allowedClients?.length ?? 0);\n if (principals === 0) {\n throw new Error(\n 'access-token verifier: name at least one allowed subject or client — this host is ' +\n 'single-tenant, and issuer + audience + scope alone admit anyone the issuer serves',\n );\n }\n return issuerUrl;\n}\n\n/** RFC 8414 §3.1 inserts the well-known segment before the issuer's path; OpenID appends it. */\nfunction metadataUrls(issuer: URL): readonly string[] {\n const path = issuer.pathname === '/' ? '' : issuer.pathname.replace(/\\/$/, '');\n return [\n `${issuer.origin}/.well-known/oauth-authorization-server${path}`,\n `${issuer.origin}${path}/.well-known/openid-configuration`,\n ];\n}\n\nfunction parseKeySet(body: unknown): readonly JWK[] | undefined {\n if (!isRecord(body) || !Array.isArray(body.keys) || body.keys.length > MAX_KEYS) return undefined;\n return body.keys.filter((key): key is JWK => isRecord(key) && typeof key.kty === 'string');\n}\n\nfunction keyAgrees(jwk: JWK, alg: TAccessTokenAlgorithm): boolean {\n const shape = KEY_SHAPE[alg];\n if (jwk.kty !== shape.kty) return false;\n if (shape.crv !== undefined && jwk.crv !== shape.crv) return false;\n if (jwk.alg !== undefined && jwk.alg !== alg) return false;\n if (jwk.use !== undefined && jwk.use !== 'sig') return false;\n if (jwk.key_ops !== undefined && !jwk.key_ops.includes('verify')) return false;\n // A public key set never carries private material; one that does is not a key set to trust.\n return jwk.d === undefined;\n}\n\nfunction refusalFor(error: unknown): TAccessTokenRefusal {\n const code = isRecord(error) ? error.code : undefined;\n const claim = isRecord(error) ? error.claim : undefined;\n switch (code) {\n case 'ERR_JWT_EXPIRED':\n return 'expired';\n case 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED':\n return 'bad-signature';\n case 'ERR_JOSE_ALG_NOT_ALLOWED':\n return 'unsupported-algorithm';\n case 'ERR_JWT_CLAIM_VALIDATION_FAILED':\n if (claim === 'iss') return 'wrong-issuer';\n if (claim === 'aud') return 'wrong-audience';\n if (claim === 'nbf') return 'not-yet-valid';\n if (claim === 'typ') return 'wrong-type';\n return 'malformed';\n default:\n return 'malformed';\n }\n}\n\nfunction normalizeTyp(typ: string): string {\n const lower = typ.toLowerCase();\n return lower.startsWith('application/') ? lower.slice('application/'.length) : lower;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,57 +1,71 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@robota-sdk/agent-transport",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "3.0.0-beta.81",
|
|
4
|
+
"description": "Transport package boundary for Robota SDK — runtime hosts are owned by agent-framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/node/index.js",
|
|
7
7
|
"types": "dist/node/index.d.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
-
"types": "./dist/node/index.d.ts",
|
|
11
10
|
"source": "./src/index.ts",
|
|
12
11
|
"node": {
|
|
13
|
-
"import":
|
|
14
|
-
|
|
12
|
+
"import": {
|
|
13
|
+
"types": "./dist/node/index.d.ts",
|
|
14
|
+
"default": "./dist/node/index.js"
|
|
15
|
+
},
|
|
16
|
+
"require": {
|
|
17
|
+
"types": "./dist/node/index.d.cts",
|
|
18
|
+
"default": "./dist/node/index.cjs"
|
|
19
|
+
}
|
|
15
20
|
},
|
|
16
|
-
"
|
|
17
|
-
"import":
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"./headless": {
|
|
22
|
-
"types": "./dist/node/headless/index.d.ts",
|
|
23
|
-
"source": "./src/headless/index.ts",
|
|
24
|
-
"node": {
|
|
25
|
-
"import": "./dist/node/headless/index.js",
|
|
26
|
-
"require": "./dist/node/headless/index.cjs"
|
|
21
|
+
"browser": {
|
|
22
|
+
"import": {
|
|
23
|
+
"types": "./dist/browser/index.d.ts",
|
|
24
|
+
"default": "./dist/browser/index.js"
|
|
25
|
+
}
|
|
27
26
|
},
|
|
28
27
|
"default": {
|
|
29
|
-
"import":
|
|
30
|
-
|
|
28
|
+
"import": {
|
|
29
|
+
"types": "./dist/node/index.d.ts",
|
|
30
|
+
"default": "./dist/node/index.js"
|
|
31
|
+
},
|
|
32
|
+
"require": {
|
|
33
|
+
"types": "./dist/node/index.d.cts",
|
|
34
|
+
"default": "./dist/node/index.cjs"
|
|
35
|
+
}
|
|
31
36
|
}
|
|
32
37
|
},
|
|
33
|
-
"./
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
38
|
+
"./client": {
|
|
39
|
+
"source": "./src/client.ts",
|
|
40
|
+
"browser": "./dist/browser/client.js",
|
|
41
|
+
"import": {
|
|
42
|
+
"types": "./dist/browser/client.d.ts",
|
|
43
|
+
"default": "./dist/browser/client.js"
|
|
39
44
|
},
|
|
40
|
-
"default":
|
|
41
|
-
"import": "./dist/node/testing/index.js",
|
|
42
|
-
"require": "./dist/node/testing/index.cjs"
|
|
43
|
-
}
|
|
45
|
+
"default": "./dist/browser/client.js"
|
|
44
46
|
},
|
|
45
|
-
"./
|
|
46
|
-
"
|
|
47
|
-
"source": "./src/programmatic/index.ts",
|
|
47
|
+
"./node": {
|
|
48
|
+
"source": "./src/node/index.ts",
|
|
48
49
|
"node": {
|
|
49
|
-
"import":
|
|
50
|
-
|
|
50
|
+
"import": {
|
|
51
|
+
"types": "./dist/node/node.d.ts",
|
|
52
|
+
"default": "./dist/node/node.js"
|
|
53
|
+
},
|
|
54
|
+
"require": {
|
|
55
|
+
"types": "./dist/node/node.d.cts",
|
|
56
|
+
"default": "./dist/node/node.cjs"
|
|
57
|
+
}
|
|
51
58
|
},
|
|
59
|
+
"browser": null,
|
|
52
60
|
"default": {
|
|
53
|
-
"import":
|
|
54
|
-
|
|
61
|
+
"import": {
|
|
62
|
+
"types": "./dist/node/node.d.ts",
|
|
63
|
+
"default": "./dist/node/node.js"
|
|
64
|
+
},
|
|
65
|
+
"require": {
|
|
66
|
+
"types": "./dist/node/node.d.cts",
|
|
67
|
+
"default": "./dist/node/node.cjs"
|
|
68
|
+
}
|
|
55
69
|
}
|
|
56
70
|
}
|
|
57
71
|
},
|
|
@@ -71,27 +85,34 @@
|
|
|
71
85
|
"src"
|
|
72
86
|
],
|
|
73
87
|
"dependencies": {
|
|
74
|
-
"
|
|
75
|
-
"@robota-sdk/agent-
|
|
76
|
-
"@robota-sdk/agent-
|
|
88
|
+
"jose": "6.2.3",
|
|
89
|
+
"@robota-sdk/agent-core": "3.0.0-beta.81",
|
|
90
|
+
"@robota-sdk/agent-interface-analytics": "3.0.0-beta.81",
|
|
91
|
+
"@robota-sdk/agent-interface-execution": "3.0.0-beta.81",
|
|
92
|
+
"@robota-sdk/agent-interface-command": "3.0.0-beta.81",
|
|
93
|
+
"@robota-sdk/agent-interface-session": "3.0.0-beta.81",
|
|
94
|
+
"@robota-sdk/agent-interface-transport": "3.0.0-beta.81",
|
|
95
|
+
"@robota-sdk/agent-interface-session-mobility": "3.0.0-beta.81"
|
|
77
96
|
},
|
|
78
97
|
"devDependencies": {
|
|
79
98
|
"rimraf": "^5.0.10",
|
|
80
|
-
"tsdown": "^0.22.
|
|
81
|
-
"tsx": "^4.
|
|
82
|
-
"typescript": "^
|
|
83
|
-
"vitest": "^3.2.6"
|
|
84
|
-
"@robota-sdk/agent-command": "3.0.0-beta.78"
|
|
99
|
+
"tsdown": "^0.22.14",
|
|
100
|
+
"tsx": "^4.23.1",
|
|
101
|
+
"typescript": "^6.0.3",
|
|
102
|
+
"vitest": "^3.2.6"
|
|
85
103
|
},
|
|
86
104
|
"license": "AGPL-3.0-only OR LicenseRef-Commercial",
|
|
87
105
|
"publishConfig": {
|
|
88
106
|
"access": "public"
|
|
89
107
|
},
|
|
108
|
+
"volta": {
|
|
109
|
+
"extends": "../../package.json"
|
|
110
|
+
},
|
|
90
111
|
"scripts": {
|
|
91
|
-
"build": "tsdown",
|
|
92
|
-
"build:js": "
|
|
93
|
-
"build:types": "
|
|
94
|
-
"typecheck": "
|
|
112
|
+
"build": "rimraf dist && tsdown",
|
|
113
|
+
"build:js": "pnpm run build",
|
|
114
|
+
"build:types": "pnpm run build",
|
|
115
|
+
"typecheck": "tsgo --noEmit",
|
|
95
116
|
"test": "vitest run --passWithNoTests",
|
|
96
117
|
"test:coverage": "vitest run --coverage --passWithNoTests",
|
|
97
118
|
"clean": "rimraf dist"
|