@roll-agent/companion 0.4.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/companion-workspace.d.ts +5 -2
- package/dist/companion-workspace.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/relay-bridge-v11.d.ts +5 -1
- package/dist/relay-bridge-v11.js +1 -1
- package/dist/relay-frame-buffer.d.ts +5 -3
- package/dist/relay-frame-buffer.js +1 -1
- package/dist/remote-app-output.d.ts +2 -0
- package/dist/remote-app-output.js +1 -0
- package/package.json +5 -5
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { RUNTIME_SERVER_REQUEST_METHODS, type InitializeResult, type PendingApproval, type RuntimeEventEnvelope, type RuntimeMethod, type RuntimeMethodInput, type RuntimeMethodResult, type RuntimeMethodResultForVersion, type RuntimeProtocolVersion } from "@roll-agent/protocol";
|
|
1
|
+
import { RUNTIME_SERVER_REQUEST_METHODS, type InitializeResult, type PendingApproval, type RuntimeEventEnvelope, type RuntimeMethod, type LatestRuntimeMethodInput, type LatestRuntimeMethodResult, type RuntimeMethodInput, type RuntimeMethodResult, type RuntimeMethodResultForVersion, type RuntimeProtocolVersion } from "@roll-agent/protocol";
|
|
2
2
|
import type { RollNodeClient, RuntimeServerRequestHandler } from "@roll-agent/client-node";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { CompanionEventBuffer, type CompanionEventBufferOptions, type EventBufferReplay } from "./event-buffer.ts";
|
|
5
5
|
import { CompanionRelayFrameBuffer, type CompanionRelayFrameEntryV11, type CompanionRelayFrameReplayV11 } from "./relay-frame-buffer.ts";
|
|
6
6
|
import { CompanionInteractionBroker, type RemoteInteractionCandidateContext } from "./interaction-broker.ts";
|
|
7
7
|
import { WorkspaceLeaseManager } from "./lease-manager.ts";
|
|
8
|
-
import { type RelayApprovalCandidateInput, type RelayApprovalCandidateParams, type RelayApprovalCandidateResult, type RelayInteractionCandidateParamsV11, type RelayInteractionCandidateResultV11, type RelayRuntimeRequest, type RelayRuntimeRequestV11, type WorkspaceId } from "@roll-agent/relay-protocol";
|
|
8
|
+
import { type RelayApprovalCandidateInput, type RelayApprovalCandidateParams, type RelayApprovalCandidateResult, type RelayInteractionCandidateParamsV11, type RelayInteractionCandidateResultV11, type RelayRuntimeRequest, type RelayRuntimeRequestV12 as RelayRuntimeRequestV11, type WorkspaceId } from "@roll-agent/relay-protocol";
|
|
9
9
|
export declare const LOCAL_APPROVAL_DECISIONS: readonly ["allow", "deny", "require-local-confirmation"];
|
|
10
10
|
export declare const localApprovalDecisionSchema: z.ZodEnum<["allow", "deny", "require-local-confirmation"]>;
|
|
11
11
|
export type LocalApprovalDecision = z.infer<typeof localApprovalDecisionSchema>;
|
|
@@ -15,6 +15,7 @@ export interface LocalApprovalPolicyContext {
|
|
|
15
15
|
export type LocalApprovalPolicy = (approval: PendingApproval, context: LocalApprovalPolicyContext) => LocalApprovalDecision | Promise<LocalApprovalDecision>;
|
|
16
16
|
export interface CompanionRuntimeClient {
|
|
17
17
|
request<TMethod extends RuntimeMethod>(method: TMethod, input: RuntimeMethodInput<TMethod>): Promise<RuntimeMethodResultForVersion<RuntimeProtocolVersion, TMethod>>;
|
|
18
|
+
getOperationResult?(input: LatestRuntimeMethodInput<"operation.result.get">): Promise<LatestRuntimeMethodResult<"operation.result.get">>;
|
|
18
19
|
onEvent(listener: (event: RuntimeEventEnvelope) => void): () => void;
|
|
19
20
|
getInitializationResult?(): Pick<InitializeResult, "protocolVersion">;
|
|
20
21
|
close(): void;
|
|
@@ -22,6 +23,7 @@ export interface CompanionRuntimeClient {
|
|
|
22
23
|
}
|
|
23
24
|
export interface CompanionWorkspaceOptions extends CompanionEventBufferOptions {
|
|
24
25
|
readonly client: CompanionRuntimeClient;
|
|
26
|
+
readonly remoteAppOutputPolicy?: (agentName: string, toolName: string) => boolean | Promise<boolean>;
|
|
25
27
|
readonly localApprovalPolicy: LocalApprovalPolicy;
|
|
26
28
|
readonly workspaceId?: WorkspaceId;
|
|
27
29
|
readonly interactionBroker?: CompanionInteractionBroker;
|
|
@@ -56,6 +58,7 @@ export declare class CompanionWorkspace {
|
|
|
56
58
|
readonly events: CompanionEventBuffer;
|
|
57
59
|
readonly relayFramesV11: CompanionRelayFrameBuffer;
|
|
58
60
|
private readonly client;
|
|
61
|
+
private readonly remoteAppOutputPolicy;
|
|
59
62
|
private readonly localApprovalPolicy;
|
|
60
63
|
private readonly protocolCapabilities;
|
|
61
64
|
private readonly interactionBroker;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{RUNTIME_METHODS as e,RUNTIME_SERVER_REQUEST_METHODS as r,getRuntimeProtocolCapabilities as t,parseRuntimeMethodParams as a}from"@roll-agent/protocol";import{z as o}from"zod";import{CompanionEventBuffer as i}from"./event-buffer.js";import{CompanionRelayFrameBuffer as n}from"./relay-frame-buffer.js";import{CompanionInteractionBroker as s}from"./interaction-broker.js";import{WorkspaceLeaseManager as l}from"./lease-manager.js";import{RELAY_REQUEST_METHODS as p,RELAY_REQUEST_METHODS_V11 as c,relayApprovalCandidateParamsSchema as d,relayInteractionCandidateParamsSchemaV11 as h}from"@roll-agent/relay-protocol";export const LOCAL_APPROVAL_DECISIONS=["allow","deny","require-local-confirmation"];export const localApprovalDecisionSchema=o.enum(LOCAL_APPROVAL_DECISIONS);export class LocalApprovalDeniedError extends Error{constructor(e){super(e),this.name="LocalApprovalDeniedError"}}export class LocalConfirmationRequiredError extends Error{constructor(e){super(e),this.name="LocalConfirmationRequiredError"}}export class InvalidRelayRequestParamsError extends Error{method;constructor(e){super(`Invalid params for Relay method "${e}"`),this.name="InvalidRelayRequestParamsError",this.method=e}}function u(e,r){try{return a(e,r)}catch{throw new InvalidRelayRequestParamsError(e)}}function v(e){const r=d.safeParse(e);if(!r.success)throw new InvalidRelayRequestParamsError(p.approvalCandidate);return r.data}function m(e){const r=h.safeParse(e);if(!r.success)throw new InvalidRelayRequestParamsError(c.interactionCandidate);return r.data}function w(e){const r=localApprovalDecisionSchema.safeParse(e);if(!r.success)throw new LocalApprovalDeniedError("Local Companion policy returned an invalid approval decision");return r.data}function y(e){return e instanceof Error?e:new Error(void 0===e?"Runtime cancelled the approval request":String(e))}async function f(e,r,t){if(r.aborted)throw y(r.reason);let a=()=>{};const o=new Promise((e,r)=>{a=r}),i=()=>{a(y(r.reason))};r.addEventListener("abort",i,{once:!0});try{const a=Promise.resolve().then(()=>{if(r.aborted)throw y(r.reason);return t(e,{signal:r})});let i;try{i=await Promise.race([a,o])}catch{if(r.aborted)throw y(r.reason);throw new Error("Local Companion policy failed")}return w(i)}finally{r.removeEventListener("abort",i)}}export class CompanionApprovalRequestBroker{leases=new l;handle=(e,r)=>this.handleApprovalRequest(e,r);pendingRequests=new Map;localApprovalPolicy;bindLocalApprovalPolicy(e){if(void 0!==this.localApprovalPolicy)throw new Error("Companion approval request broker is already bound to a workspace");this.localApprovalPolicy=e;let r=!1;return()=>{r||(r=!0,this.localApprovalPolicy===e&&(this.localApprovalPolicy=void 0))}}async submitCandidate(e){const r=this.pendingRequests.get(e.approvalId);if(void 0===r)throw new LocalApprovalDeniedError(`Approval "${e.approvalId}" is no longer pending`);if(r.params.threadId!==e.threadId||r.params.approval.turnId!==e.turnId)throw new LocalApprovalDeniedError(`Approval "${e.approvalId}" does not belong to the requested thread and turn`);if("reject"===e.decision)return r.activeApproveController?.abort(new LocalApprovalDeniedError(`Approval "${e.approvalId}" approval candidate was superseded by rejection`)),this.assertStillPending(r),this.resolvePending(r,{decision:"reject",...void 0!==e.reason?{reason:e.reason}:{}}),{accepted:!0};if(void 0!==r.activeApproveController)throw new LocalApprovalDeniedError(`Approval "${e.approvalId}" already has an approval candidate in progress`);const t=new AbortController;r.activeApproveController=t;try{const e=this.localApprovalPolicy;if(void 0===e)throw new LocalApprovalDeniedError("Companion approval request broker is not bound to a local policy");const a=await f(r.params.approval,AbortSignal.any([r.signal,t.signal]),e);if(this.assertStillPending(r),"deny"===a)throw this.resolvePending(r,{decision:"reject",reason:"Local Companion policy denied remote approval"}),new LocalApprovalDeniedError("Local Companion policy denied remote approval");if("require-local-confirmation"===a)throw new LocalConfirmationRequiredError("Local confirmation is required before this approval can continue");return this.assertStillPending(r),this.resolvePending(r,{decision:"approve"}),{accepted:!0}}catch(t){throw t instanceof LocalConfirmationRequiredError||this.pendingRequests.get(e.approvalId)!==r||this.rejectPending(r,t instanceof Error?t:new LocalApprovalDeniedError("Local Companion policy failed")),t}finally{r.activeApproveController===t&&(r.activeApproveController=void 0)}}handleApprovalRequest(e,r){if(r.signal.aborted)return Promise.reject(y(r.signal.reason));const t=this.pendingRequests.get(e.approval.id);if(void 0!==t)return Promise.reject(new Error(`Approval "${e.approval.id}" is already pending for Runtime request `+JSON.stringify(t.requestId)));let a=()=>{},o=()=>{};const i=new Promise((e,r)=>{a=e,o=r}),n={params:e,requestId:r.requestId,signal:r.signal,resolve:a,reject:o,releaseLease:this.leases.acquire({kind:"approval",id:e.approval.id}),activeApproveController:void 0},s=()=>{this.rejectPending(n,y(r.signal.reason))};return this.pendingRequests.set(e.approval.id,n),r.signal.addEventListener("abort",s,{once:!0}),r.signal.aborted&&s(),i.finally(()=>{r.signal.removeEventListener("abort",s)})}assertStillPending(e){if(this.pendingRequests.get(e.params.approval.id)!==e)throw new LocalApprovalDeniedError(`Approval "${e.params.approval.id}" is no longer pending`)}resolvePending(e,r){this.pendingRequests.delete(e.params.approval.id),e.releaseLease(),e.resolve(r)}rejectPending(e,r){this.pendingRequests.get(e.params.approval.id)===e&&(this.pendingRequests.delete(e.params.approval.id),e.releaseLease(),e.reject(r))}}export class CompanionWorkspace{leases;events;relayFramesV11;client;localApprovalPolicy;protocolCapabilities;interactionBroker;approvalRequestBroker;approvals=new Map;eventListeners=new Set;relayFrameListenersV11=new Set;releaseClientSubscription;releaseApprovalPolicyBinding;releaseInteractionBinding;closed=!1;constructor(e){this.client=e.client,this.localApprovalPolicy=e.localApprovalPolicy;const r=this.client.getInitializationResult?.().protocolVersion??"1.0";if(this.protocolCapabilities=t(r),this.interactionBroker=e.interactionBroker,this.approvalRequestBroker=e.approvalRequestBroker,void 0!==this.interactionBroker&&void 0!==this.approvalRequestBroker)throw new Error("Provide either interactionBroker or the deprecated approvalRequestBroker, not both");if(this.protocolCapabilities.serverRequestCapabilityNegotiation&&void 0===this.interactionBroker)throw new Error(`Runtime Protocol ${r} requires a CompanionInteractionBroker`);if(this.protocolCapabilities.serverRequests&&void 0===this.interactionBroker&&void 0===this.approvalRequestBroker)throw new Error("The negotiated Runtime Protocol requires a CompanionApprovalRequestBroker (deprecated) or CompanionInteractionBroker registered during client initialization");this.events=new i({...void 0!==e.maxEvents?{maxEvents:e.maxEvents}:{},...void 0!==e.maxBytes?{maxBytes:e.maxBytes}:{}}),this.relayFramesV11=new n({...void 0!==e.maxEvents?{maxEvents:e.maxEvents}:{},...void 0!==e.maxBytes?{maxBytes:e.maxBytes}:{}}),this.leases=this.interactionBroker?.leases??this.approvalRequestBroker?.leases??new l;const a=this.approvalRequestBroker?.bindLocalApprovalPolicy(this.localApprovalPolicy);let o;if(void 0!==this.interactionBroker){if(void 0===e.workspaceId)throw a?.(),new Error("workspaceId is required when interactionBroker is configured");o=this.interactionBroker.bindWorkspace({workspaceId:e.workspaceId,localApprovalPolicy:this.localApprovalPolicy,publish:e=>{const r=this.relayFramesV11.appendInteraction(e);this.publishRelayFrameV11(r)}})}try{this.releaseClientSubscription=this.client.onEvent(e=>this.handleEvent(e))}catch(e){throw o?.(),a?.(),e}this.releaseApprovalPolicyBinding=a,this.releaseInteractionBinding=o}attachBrowser(e){return this.leases.acquire({kind:"client",id:e})}detachBrowser(e){return this.leases.releaseClient(e)}acquireBackgroundShellLease(e){return this.leases.acquire({kind:"shell-session",id:e})}onBufferedEvent(e){return this.eventListeners.add(e),()=>{this.eventListeners.delete(e)}}onBufferedRelayFrameV11(e){return this.relayFrameListenersV11.add(e),()=>{this.relayFrameListenersV11.delete(e)}}replay(e=-1){return this.events.replay(e)}acknowledge(e){this.events.acknowledge(e)}replayRelayFramesV11(e=-1){return this.relayFramesV11.replay(e)}acknowledgeRelayFramesV11(e){this.relayFramesV11.acknowledge(e)}async startTurn(r){const t=a(e.turnStart,r),o=this.leases.acquire({kind:"turn",id:t.turnId});try{return await this.client.request(e.turnStart,t)}catch(e){throw o(),e}}async respondApproval(r){const t=a(e.approvalRespond,r);if(!this.protocolCapabilities.clientApprovalResponses)throw new LocalApprovalDeniedError("The negotiated Runtime Protocol must use the Relay approval.candidate method");const o=this.approvals.get(t.approvalId);if(void 0===o)throw new LocalApprovalDeniedError(`Approval "${t.approvalId}" is no longer pending`);if("approve"===t.decision){const e=await f(o.approval,o.controller.signal,this.localApprovalPolicy);if(o.controller.signal.aborted||this.approvals.get(t.approvalId)!==o)throw y(o.controller.signal.reason);if("deny"===e)throw new LocalApprovalDeniedError("Local Companion policy denied remote approval");if("require-local-confirmation"===e)throw new LocalConfirmationRequiredError("Local confirmation is required before this approval can continue")}const i=await this.client.request(e.approvalRespond,t);return this.approvals.get(t.approvalId)===o&&(o.controller.abort(new LocalApprovalDeniedError(`Approval "${t.approvalId}" was resolved by another response`)),this.approvals.delete(t.approvalId),this.leases.release({kind:"approval",id:t.approvalId})),i}async submitApprovalCandidate(e){const r=v(e);if(!this.protocolCapabilities.serverRequests)throw new LocalApprovalDeniedError("Relay approval.candidate requires Runtime server-request capability");if(void 0!==this.interactionBroker)return this.interactionBroker.submitLegacyApprovalCandidate(r);const t=this.approvalRequestBroker;if(void 0===t)throw new LocalApprovalDeniedError("Runtime server-request approval broker is unavailable");return t.submitCandidate(r)}async submitInteractionCandidateV11(e,r){const t=m(e),a=this.interactionBroker;if(void 0===a)throw new LocalApprovalDeniedError("Relay interaction.candidate requires CompanionInteractionBroker");return a.submitCandidate(t,r)}async handleRemoteRequest(r){switch(r.method){case p.approvalCandidate:return this.submitApprovalCandidate(v(r.params));case e.initialize:throw new LocalApprovalDeniedError("initialize is owned by the local Companion and cannot be relayed");case e.threadList:return this.client.request(e.threadList,u(e.threadList,r.params));case e.threadCreate:return this.client.request(e.threadCreate,u(e.threadCreate,r.params));case e.threadOpen:return this.client.request(e.threadOpen,u(e.threadOpen,r.params));case e.threadSnapshot:return this.client.request(e.threadSnapshot,u(e.threadSnapshot,r.params));case e.threadRename:return this.client.request(e.threadRename,u(e.threadRename,r.params));case e.threadDelete:return this.client.request(e.threadDelete,u(e.threadDelete,r.params));case e.threadDetach:return this.client.request(e.threadDetach,u(e.threadDetach,r.params));case e.threadCapabilities:return this.client.request(e.threadCapabilities,u(e.threadCapabilities,r.params));case e.turnStart:return this.startTurn(u(e.turnStart,r.params));case e.turnCancel:return this.client.request(e.turnCancel,u(e.turnCancel,r.params));case e.approvalRespond:return this.respondApproval(u(e.approvalRespond,r.params));case e.operationGet:return this.client.request(e.operationGet,u(e.operationGet,r.params))}}async handleRemoteRequestV11(r,t){switch(r.method){case c.interactionCandidate:return this.submitInteractionCandidateV11(m(r.params),t);case c.initialize:throw new LocalApprovalDeniedError("initialize is owned by the local Companion and cannot be relayed");case c.threadList:return this.client.request(e.threadList,u(e.threadList,r.params));case c.threadCreate:return this.client.request(e.threadCreate,u(e.threadCreate,r.params));case c.threadOpen:return this.client.request(e.threadOpen,u(e.threadOpen,r.params));case c.threadSnapshot:return this.client.request(e.threadSnapshot,u(e.threadSnapshot,r.params));case c.threadRename:return this.client.request(e.threadRename,u(e.threadRename,r.params));case c.threadDelete:return this.client.request(e.threadDelete,u(e.threadDelete,r.params));case c.threadDetach:return this.client.request(e.threadDetach,u(e.threadDetach,r.params));case c.threadCapabilities:return this.client.request(e.threadCapabilities,u(e.threadCapabilities,r.params));case c.turnStart:return this.startTurn(u(e.turnStart,r.params));case c.turnCancel:return this.client.request(e.turnCancel,u(e.turnCancel,r.params));case c.operationGet:return this.client.request(e.operationGet,u(e.operationGet,r.params))}}closeRemoteInteractions(e){this.interactionBroker?.close(e)}async closeIfIdle(){if(this.closed)return!0;if(!this.leases.canStopRuntime())return!1;this.closed=!0,this.interactionBroker?.close(new Error("Companion workspace closed"));try{this.releaseClientSubscription()}finally{this.releaseInteractionBinding?.(),this.releaseApprovalPolicyBinding?.()}return void 0===this.client.shutdown?this.client.close():await this.client.shutdown(),!0}handleEvent(e){this.updateLeases(e);const r=this.events.append(e);for(const e of this.eventListeners)try{e(r)}catch{}const t=this.relayFramesV11.appendRuntimeEvent(e);void 0!==t&&this.publishRelayFrameV11(t)}publishRelayFrameV11(e){for(const r of this.relayFrameListenersV11)try{r(e)}catch{}}updateLeases(e){if(void 0===e.turnId||"turn.completed"!==e.event.type&&"turn.cancelled"!==e.event.type&&"turn.failed"!==e.event.type){if(this.protocolCapabilities.clientApprovalResponses&&"approval.required"===e.event.type){const r=e.event.approval,t=this.approvals.get(r.id);t?.controller.abort(new Error(`Approval "${r.id}" was superseded`)),this.approvals.set(r.id,{approval:r,controller:new AbortController}),this.leases.acquire({kind:"approval",id:r.id})}}else if(this.leases.release({kind:"turn",id:e.turnId}),this.interactionBroker?.cancelTurn(e.threadId,e.turnId,new Error(`Turn ended with ${e.event.type}`)),this.protocolCapabilities.clientApprovalResponses)for(const[r,t]of this.approvals)t.approval.turnId===e.turnId&&(this.approvals.delete(r),t.controller.abort(new Error(`Turn ended with ${e.event.type}`)),this.leases.release({kind:"approval",id:r}))}}export function isRollNodeClient(e){return"getOutcomeUnknownTurnIds"in e}
|
|
1
|
+
import{projectRemoteAppOutputCapabilities as e}from"./remote-app-output.js";import{RUNTIME_METHODS as r,RUNTIME_SERVER_REQUEST_METHODS as t,getRuntimeProtocolCapabilities as a,parseRuntimeMethodParams as o,operationResultGetResultSchema as i,parseLatestRuntimeMethodParams as n}from"@roll-agent/protocol";import{z as s}from"zod";import{CompanionEventBuffer as l}from"./event-buffer.js";import{CompanionRelayFrameBuffer as p}from"./relay-frame-buffer.js";import{CompanionInteractionBroker as c}from"./interaction-broker.js";import{WorkspaceLeaseManager as d}from"./lease-manager.js";import{RELAY_REQUEST_METHODS as h,RELAY_REQUEST_METHODS_V11 as u,relayApprovalCandidateParamsSchema as v,relayInteractionCandidateParamsSchemaV11 as m}from"@roll-agent/relay-protocol";export const LOCAL_APPROVAL_DECISIONS=["allow","deny","require-local-confirmation"];export const localApprovalDecisionSchema=s.enum(LOCAL_APPROVAL_DECISIONS);export class LocalApprovalDeniedError extends Error{constructor(e){super(e),this.name="LocalApprovalDeniedError"}}export class LocalConfirmationRequiredError extends Error{constructor(e){super(e),this.name="LocalConfirmationRequiredError"}}export class InvalidRelayRequestParamsError extends Error{method;constructor(e){super(`Invalid params for Relay method "${e}"`),this.name="InvalidRelayRequestParamsError",this.method=e}}function w(e,r){try{return o(e,r)}catch{throw new InvalidRelayRequestParamsError(e)}}function y(e){const r=v.safeParse(e);if(!r.success)throw new InvalidRelayRequestParamsError(h.approvalCandidate);return r.data}function f(e){const r=m.safeParse(e);if(!r.success)throw new InvalidRelayRequestParamsError(u.interactionCandidate);return r.data}function R(e){const r=localApprovalDecisionSchema.safeParse(e);if(!r.success)throw new LocalApprovalDeniedError("Local Companion policy returned an invalid approval decision");return r.data}function g(e){return e instanceof Error?e:new Error(void 0===e?"Runtime cancelled the approval request":String(e))}async function q(e,r,t){if(r.aborted)throw g(r.reason);let a=()=>{};const o=new Promise((e,r)=>{a=r}),i=()=>{a(g(r.reason))};r.addEventListener("abort",i,{once:!0});try{const a=Promise.resolve().then(()=>{if(r.aborted)throw g(r.reason);return t(e,{signal:r})});let i;try{i=await Promise.race([a,o])}catch{if(r.aborted)throw g(r.reason);throw new Error("Local Companion policy failed")}return R(i)}finally{r.removeEventListener("abort",i)}}export class CompanionApprovalRequestBroker{leases=new d;handle=(e,r)=>this.handleApprovalRequest(e,r);pendingRequests=new Map;localApprovalPolicy;bindLocalApprovalPolicy(e){if(void 0!==this.localApprovalPolicy)throw new Error("Companion approval request broker is already bound to a workspace");this.localApprovalPolicy=e;let r=!1;return()=>{r||(r=!0,this.localApprovalPolicy===e&&(this.localApprovalPolicy=void 0))}}async submitCandidate(e){const r=this.pendingRequests.get(e.approvalId);if(void 0===r)throw new LocalApprovalDeniedError(`Approval "${e.approvalId}" is no longer pending`);if(r.params.threadId!==e.threadId||r.params.approval.turnId!==e.turnId)throw new LocalApprovalDeniedError(`Approval "${e.approvalId}" does not belong to the requested thread and turn`);if("reject"===e.decision)return r.activeApproveController?.abort(new LocalApprovalDeniedError(`Approval "${e.approvalId}" approval candidate was superseded by rejection`)),this.assertStillPending(r),this.resolvePending(r,{decision:"reject",...void 0!==e.reason?{reason:e.reason}:{}}),{accepted:!0};if(void 0!==r.activeApproveController)throw new LocalApprovalDeniedError(`Approval "${e.approvalId}" already has an approval candidate in progress`);const t=new AbortController;r.activeApproveController=t;try{const e=this.localApprovalPolicy;if(void 0===e)throw new LocalApprovalDeniedError("Companion approval request broker is not bound to a local policy");const a=await q(r.params.approval,AbortSignal.any([r.signal,t.signal]),e);if(this.assertStillPending(r),"deny"===a)throw this.resolvePending(r,{decision:"reject",reason:"Local Companion policy denied remote approval"}),new LocalApprovalDeniedError("Local Companion policy denied remote approval");if("require-local-confirmation"===a)throw new LocalConfirmationRequiredError("Local confirmation is required before this approval can continue");return this.assertStillPending(r),this.resolvePending(r,{decision:"approve"}),{accepted:!0}}catch(t){throw t instanceof LocalConfirmationRequiredError||this.pendingRequests.get(e.approvalId)!==r||this.rejectPending(r,t instanceof Error?t:new LocalApprovalDeniedError("Local Companion policy failed")),t}finally{r.activeApproveController===t&&(r.activeApproveController=void 0)}}handleApprovalRequest(e,r){if(r.signal.aborted)return Promise.reject(g(r.signal.reason));const t=this.pendingRequests.get(e.approval.id);if(void 0!==t)return Promise.reject(new Error(`Approval "${e.approval.id}" is already pending for Runtime request `+JSON.stringify(t.requestId)));let a=()=>{},o=()=>{};const i=new Promise((e,r)=>{a=e,o=r}),n={params:e,requestId:r.requestId,signal:r.signal,resolve:a,reject:o,releaseLease:this.leases.acquire({kind:"approval",id:e.approval.id}),activeApproveController:void 0},s=()=>{this.rejectPending(n,g(r.signal.reason))};return this.pendingRequests.set(e.approval.id,n),r.signal.addEventListener("abort",s,{once:!0}),r.signal.aborted&&s(),i.finally(()=>{r.signal.removeEventListener("abort",s)})}assertStillPending(e){if(this.pendingRequests.get(e.params.approval.id)!==e)throw new LocalApprovalDeniedError(`Approval "${e.params.approval.id}" is no longer pending`)}resolvePending(e,r){this.pendingRequests.delete(e.params.approval.id),e.releaseLease(),e.resolve(r)}rejectPending(e,r){this.pendingRequests.get(e.params.approval.id)===e&&(this.pendingRequests.delete(e.params.approval.id),e.releaseLease(),e.reject(r))}}export class CompanionWorkspace{leases;events;relayFramesV11;client;remoteAppOutputPolicy;localApprovalPolicy;protocolCapabilities;interactionBroker;approvalRequestBroker;approvals=new Map;eventListeners=new Set;relayFrameListenersV11=new Set;releaseClientSubscription;releaseApprovalPolicyBinding;releaseInteractionBinding;closed=!1;constructor(e){this.client=e.client,this.remoteAppOutputPolicy=e.remoteAppOutputPolicy??(()=>!1),this.localApprovalPolicy=e.localApprovalPolicy;const r=this.client.getInitializationResult?.().protocolVersion??"1.0";if(this.protocolCapabilities=a(r),this.interactionBroker=e.interactionBroker,this.approvalRequestBroker=e.approvalRequestBroker,void 0!==this.interactionBroker&&void 0!==this.approvalRequestBroker)throw new Error("Provide either interactionBroker or the deprecated approvalRequestBroker, not both");if(this.protocolCapabilities.serverRequestCapabilityNegotiation&&void 0===this.interactionBroker)throw new Error(`Runtime Protocol ${r} requires a CompanionInteractionBroker`);if(this.protocolCapabilities.serverRequests&&void 0===this.interactionBroker&&void 0===this.approvalRequestBroker)throw new Error("The negotiated Runtime Protocol requires a CompanionApprovalRequestBroker (deprecated) or CompanionInteractionBroker registered during client initialization");this.events=new l({...void 0!==e.maxEvents?{maxEvents:e.maxEvents}:{},...void 0!==e.maxBytes?{maxBytes:e.maxBytes}:{}}),this.relayFramesV11=new p({...void 0!==e.maxEvents?{maxEvents:e.maxEvents}:{},...void 0!==e.maxBytes?{maxBytes:e.maxBytes}:{}}),this.leases=this.interactionBroker?.leases??this.approvalRequestBroker?.leases??new d;const t=this.approvalRequestBroker?.bindLocalApprovalPolicy(this.localApprovalPolicy);let o;if(void 0!==this.interactionBroker){if(void 0===e.workspaceId)throw t?.(),new Error("workspaceId is required when interactionBroker is configured");o=this.interactionBroker.bindWorkspace({workspaceId:e.workspaceId,localApprovalPolicy:this.localApprovalPolicy,publish:e=>{const r=this.relayFramesV11.appendInteraction(e);this.publishRelayFrameV11(r)}})}try{this.releaseClientSubscription=this.client.onEvent(e=>this.handleEvent(e))}catch(e){throw o?.(),t?.(),e}this.releaseApprovalPolicyBinding=t,this.releaseInteractionBinding=o}attachBrowser(e){return this.leases.acquire({kind:"client",id:e})}detachBrowser(e){return this.leases.releaseClient(e)}acquireBackgroundShellLease(e){return this.leases.acquire({kind:"shell-session",id:e})}onBufferedEvent(e){return this.eventListeners.add(e),()=>{this.eventListeners.delete(e)}}onBufferedRelayFrameV11(e){return this.relayFrameListenersV11.add(e),()=>{this.relayFrameListenersV11.delete(e)}}replay(e=-1){return this.events.replay(e)}acknowledge(e){this.events.acknowledge(e)}replayRelayFramesV11(e=-1){return this.relayFramesV11.replay(e)}acknowledgeRelayFramesV11(e){this.relayFramesV11.acknowledge(e)}async startTurn(e){const t=o(r.turnStart,e),a=this.leases.acquire({kind:"turn",id:t.turnId});try{return await this.client.request(r.turnStart,t)}catch(e){throw a(),e}}async respondApproval(e){const t=o(r.approvalRespond,e);if(!this.protocolCapabilities.clientApprovalResponses)throw new LocalApprovalDeniedError("The negotiated Runtime Protocol must use the Relay approval.candidate method");const a=this.approvals.get(t.approvalId);if(void 0===a)throw new LocalApprovalDeniedError(`Approval "${t.approvalId}" is no longer pending`);if("approve"===t.decision){const e=await q(a.approval,a.controller.signal,this.localApprovalPolicy);if(a.controller.signal.aborted||this.approvals.get(t.approvalId)!==a)throw g(a.controller.signal.reason);if("deny"===e)throw new LocalApprovalDeniedError("Local Companion policy denied remote approval");if("require-local-confirmation"===e)throw new LocalConfirmationRequiredError("Local confirmation is required before this approval can continue")}const i=await this.client.request(r.approvalRespond,t);return this.approvals.get(t.approvalId)===a&&(a.controller.abort(new LocalApprovalDeniedError(`Approval "${t.approvalId}" was resolved by another response`)),this.approvals.delete(t.approvalId),this.leases.release({kind:"approval",id:t.approvalId})),i}async submitApprovalCandidate(e){const r=y(e);if(!this.protocolCapabilities.serverRequests)throw new LocalApprovalDeniedError("Relay approval.candidate requires Runtime server-request capability");if(void 0!==this.interactionBroker)return this.interactionBroker.submitLegacyApprovalCandidate(r);const t=this.approvalRequestBroker;if(void 0===t)throw new LocalApprovalDeniedError("Runtime server-request approval broker is unavailable");return t.submitCandidate(r)}async submitInteractionCandidateV11(e,r){const t=f(e),a=this.interactionBroker;if(void 0===a)throw new LocalApprovalDeniedError("Relay interaction.candidate requires CompanionInteractionBroker");return a.submitCandidate(t,r)}async handleRemoteRequest(e){switch(e.method){case h.approvalCandidate:return this.submitApprovalCandidate(y(e.params));case r.initialize:throw new LocalApprovalDeniedError("initialize is owned by the local Companion and cannot be relayed");case r.threadList:return this.client.request(r.threadList,w(r.threadList,e.params));case r.threadCreate:return this.client.request(r.threadCreate,w(r.threadCreate,e.params));case r.threadOpen:return this.client.request(r.threadOpen,w(r.threadOpen,e.params));case r.threadSnapshot:return this.client.request(r.threadSnapshot,w(r.threadSnapshot,e.params));case r.threadRename:return this.client.request(r.threadRename,w(r.threadRename,e.params));case r.threadDelete:return this.client.request(r.threadDelete,w(r.threadDelete,e.params));case r.threadDetach:return this.client.request(r.threadDetach,w(r.threadDetach,e.params));case r.threadCapabilities:return this.client.request(r.threadCapabilities,w(r.threadCapabilities,e.params));case r.turnStart:return this.startTurn(w(r.turnStart,e.params));case r.turnCancel:return this.client.request(r.turnCancel,w(r.turnCancel,e.params));case r.approvalRespond:return this.respondApproval(w(r.approvalRespond,e.params));case r.operationGet:return this.client.request(r.operationGet,w(r.operationGet,e.params))}}async handleRemoteRequestV11(t,a){if(t.workspaceId!==a.workspaceId||a.signal.aborted)throw new LocalApprovalDeniedError("Remote Workspace access denied");switch(t.method){case"operation.result.get":{if("1.5"!==this.client.getInitializationResult?.().protocolVersion||!this.client.getOperationResult)throw new LocalApprovalDeniedError("Runtime does not support App results");const e=i.parse(await this.client.getOperationResult(n(r.operationResultGet,t.params)));if(null===e.result)return e;const o=e.result.output;return!await this.remoteAppOutputPolicy(e.result.agentName,e.result.toolName)||a.signal.aborted||"available"===o.status&&!o.remoteReadable?{result:{...e.result,output:{status:"denied"}}}:e}case u.interactionCandidate:return this.submitInteractionCandidateV11(f(t.params),a);case u.initialize:throw new LocalApprovalDeniedError("initialize is owned by the local Companion and cannot be relayed");case u.threadList:return this.client.request(r.threadList,w(r.threadList,t.params));case u.threadCreate:return this.client.request(r.threadCreate,w(r.threadCreate,t.params));case u.threadOpen:return this.client.request(r.threadOpen,w(r.threadOpen,t.params));case u.threadSnapshot:return this.client.request(r.threadSnapshot,w(r.threadSnapshot,t.params));case u.threadRename:return this.client.request(r.threadRename,w(r.threadRename,t.params));case u.threadDelete:return this.client.request(r.threadDelete,w(r.threadDelete,t.params));case u.threadDetach:return this.client.request(r.threadDetach,w(r.threadDetach,t.params));case u.threadCapabilities:return e(await this.client.request(r.threadCapabilities,w(r.threadCapabilities,t.params)),"1.5"===this.client.getInitializationResult?.().protocolVersion&&void 0!==this.client.getOperationResult,async(e,r)=>!a.signal.aborted&&await this.remoteAppOutputPolicy(e,r));case u.turnStart:return this.startTurn(w(r.turnStart,t.params));case u.turnCancel:return this.client.request(r.turnCancel,w(r.turnCancel,t.params));case u.operationGet:return this.client.request(r.operationGet,w(r.operationGet,t.params))}}closeRemoteInteractions(e){this.interactionBroker?.close(e)}async closeIfIdle(){if(this.closed)return!0;if(!this.leases.canStopRuntime())return!1;this.closed=!0,this.interactionBroker?.close(new Error("Companion workspace closed"));try{this.releaseClientSubscription()}finally{this.releaseInteractionBinding?.(),this.releaseApprovalPolicyBinding?.()}return void 0===this.client.shutdown?this.client.close():await this.client.shutdown(),!0}handleEvent(e){this.updateLeases(e);const r=this.events.append(e);for(const e of this.eventListeners)try{e(r)}catch{}const t=this.relayFramesV11.appendRuntimeEvent(e);void 0!==t&&this.publishRelayFrameV11(t)}publishRelayFrameV11(e){for(const r of this.relayFrameListenersV11)try{r(e)}catch{}}updateLeases(e){if(void 0===e.turnId||"turn.completed"!==e.event.type&&"turn.cancelled"!==e.event.type&&"turn.failed"!==e.event.type){if(this.protocolCapabilities.clientApprovalResponses&&"approval.required"===e.event.type){const r=e.event.approval,t=this.approvals.get(r.id);t?.controller.abort(new Error(`Approval "${r.id}" was superseded`)),this.approvals.set(r.id,{approval:r,controller:new AbortController}),this.leases.acquire({kind:"approval",id:r.id})}}else if(this.leases.release({kind:"turn",id:e.turnId}),this.interactionBroker?.cancelTurn(e.threadId,e.turnId,new Error(`Turn ended with ${e.event.type}`)),this.protocolCapabilities.clientApprovalResponses)for(const[r,t]of this.approvals)t.approval.turnId===e.turnId&&(this.approvals.delete(r),t.controller.abort(new Error(`Turn ended with ${e.event.type}`)),this.leases.release({kind:"approval",id:r}))}}export function isRollNodeClient(e){return"getOutcomeUnknownTurnIds"in e}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { COMPANION_RELAY_PROTOCOL_VERSION, RELAY_REQUEST_METHODS, deviceIdSchema
|
|
|
2
2
|
export type { DeviceId, WorkspaceId, RelayRequestId, RelayRequestMethod, RelayApprovalCandidateInput, RelayApprovalCandidateParams, RelayApprovalCandidateResult, RelayMessage, RelayRuntimeRequest, RelayRuntimeResponse, RelayRuntimeEvent, RelayGap, RelayEncryptedMessage, } from "./relay-protocol.ts";
|
|
3
3
|
export { CompanionEventBuffer, DEFAULT_COMPANION_MAX_BYTES, DEFAULT_COMPANION_MAX_EVENTS, } from "./event-buffer.ts";
|
|
4
4
|
export type { BufferedRuntimeEvent, CompanionEventBufferOptions, EventBufferGap, EventBufferReplay, } from "./event-buffer.ts";
|
|
5
|
-
export { CompanionRelayFrameBuffer, DEFAULT_COMPANION_RELAY_FRAME_MAX_BYTES, DEFAULT_COMPANION_RELAY_FRAME_MAX_EVENTS, materializeRelayFrameV11, } from "./relay-frame-buffer.ts";
|
|
5
|
+
export { CompanionRelayFrameBuffer, DEFAULT_COMPANION_RELAY_FRAME_MAX_BYTES, DEFAULT_COMPANION_RELAY_FRAME_MAX_EVENTS, materializeRelayFrameV11, materializeRelayFrameV12, } from "./relay-frame-buffer.ts";
|
|
6
6
|
export type { BufferedRelayInteractionFrameV11, BufferedRelayRuntimeEventV11, CompanionRelayFrameBufferOptions, CompanionRelayFrameEntryV11, CompanionRelayFrameReplayV11, RelayFrameBufferGapV11, } from "./relay-frame-buffer.ts";
|
|
7
7
|
export { COMPANION_LEASE_KINDS, WorkspaceLeaseManager } from "./lease-manager.ts";
|
|
8
8
|
export type { CompanionLease, CompanionLeaseKind } from "./lease-manager.ts";
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{COMPANION_RELAY_PROTOCOL_VERSION,RELAY_REQUEST_METHODS,deviceIdSchema,workspaceIdSchema,relayRequestIdSchema,relayApprovalCandidateParamsSchema,relayApprovalCandidateResultSchema,relayDeviceConnectSchema,relayRuntimeRequestSchema,relayRuntimeResponseSchema,relayRuntimeEventSchema,relayAckSchema,relayGapSchema,relayEncryptedMessageSchema,relayMessageSchema}from"./relay-protocol.js";export{CompanionEventBuffer,DEFAULT_COMPANION_MAX_BYTES,DEFAULT_COMPANION_MAX_EVENTS}from"./event-buffer.js";export{CompanionRelayFrameBuffer,DEFAULT_COMPANION_RELAY_FRAME_MAX_BYTES,DEFAULT_COMPANION_RELAY_FRAME_MAX_EVENTS,materializeRelayFrameV11}from"./relay-frame-buffer.js";export{COMPANION_LEASE_KINDS,WorkspaceLeaseManager}from"./lease-manager.js";export{CompanionInteractionBroker,createRuntimeServerRequestHandlers}from"./interaction-broker.js";export{CompanionApprovalRequestBroker,CompanionWorkspace,InvalidRelayRequestParamsError,LOCAL_APPROVAL_DECISIONS,localApprovalDecisionSchema,LocalApprovalDeniedError,LocalConfirmationRequiredError,isRollNodeClient}from"./companion-workspace.js";export{CompanionRelayBridge,OutboundCompanionRelay,createWebSocketRelayTransport,relayEventMessage}from"./relay-bridge.js";export{CompanionRelayBridgeV11,OutboundCompanionRelayV11,createWebSocketRelayTransportV11}from"./relay-bridge-v11.js";
|
|
1
|
+
export{COMPANION_RELAY_PROTOCOL_VERSION,RELAY_REQUEST_METHODS,deviceIdSchema,workspaceIdSchema,relayRequestIdSchema,relayApprovalCandidateParamsSchema,relayApprovalCandidateResultSchema,relayDeviceConnectSchema,relayRuntimeRequestSchema,relayRuntimeResponseSchema,relayRuntimeEventSchema,relayAckSchema,relayGapSchema,relayEncryptedMessageSchema,relayMessageSchema}from"./relay-protocol.js";export{CompanionEventBuffer,DEFAULT_COMPANION_MAX_BYTES,DEFAULT_COMPANION_MAX_EVENTS}from"./event-buffer.js";export{CompanionRelayFrameBuffer,DEFAULT_COMPANION_RELAY_FRAME_MAX_BYTES,DEFAULT_COMPANION_RELAY_FRAME_MAX_EVENTS,materializeRelayFrameV11,materializeRelayFrameV12}from"./relay-frame-buffer.js";export{COMPANION_LEASE_KINDS,WorkspaceLeaseManager}from"./lease-manager.js";export{CompanionInteractionBroker,createRuntimeServerRequestHandlers}from"./interaction-broker.js";export{CompanionApprovalRequestBroker,CompanionWorkspace,InvalidRelayRequestParamsError,LOCAL_APPROVAL_DECISIONS,localApprovalDecisionSchema,LocalApprovalDeniedError,LocalConfirmationRequiredError,isRollNodeClient}from"./companion-workspace.js";export{CompanionRelayBridge,OutboundCompanionRelay,createWebSocketRelayTransport,relayEventMessage}from"./relay-bridge.js";export{CompanionRelayBridgeV11,OutboundCompanionRelayV11,createWebSocketRelayTransportV11}from"./relay-bridge-v11.js";
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type JsonValue } from "@roll-agent/protocol";
|
|
2
|
-
import { type DeviceId, type RelayEncryptedMessageV11, type RelayMessageV11, type RelayRequestId, type RelayRequestMethodV11, type RelayRuntimeRequestV11, type WorkspaceId } from "@roll-agent/relay-protocol";
|
|
2
|
+
import { type DeviceId, type RelayEncryptedMessageV11, type RelayMessageV12, type RelayMessageV11 as LegacyRelayMessageV11, type RelayRequestId, type RelayRequestMethodV12 as RelayRequestMethodV11, type RelayRuntimeRequestV12 as RelayRuntimeRequestV11, type WorkspaceId } from "@roll-agent/relay-protocol";
|
|
3
3
|
import type { RemoteInteractionCandidateContext, RemoteInteractionResponderPolicy } from "./interaction-broker.ts";
|
|
4
4
|
import { type CompanionRelayFrameEntryV11, type CompanionRelayFrameReplayV11 } from "./relay-frame-buffer.ts";
|
|
5
|
+
type RelayMessageV11 = RelayMessageV12 | LegacyRelayMessageV11;
|
|
5
6
|
export interface RelayTransportV11 {
|
|
6
7
|
send(message: RelayMessageV11): void | Promise<void>;
|
|
7
8
|
onMessage(listener: (message: unknown) => void): () => void;
|
|
@@ -29,6 +30,7 @@ export interface CompanionRelayBridgeV11Options {
|
|
|
29
30
|
readonly workspaces: ReadonlyMap<WorkspaceId, CompanionWorkspaceV11Port>;
|
|
30
31
|
readonly ciphers?: ReadonlyMap<WorkspaceId, RelayPayloadCipherV11>;
|
|
31
32
|
readonly maxRequestCacheEntries?: number;
|
|
33
|
+
readonly protocolVersion?: "1.1" | "1.2";
|
|
32
34
|
}
|
|
33
35
|
export interface CompanionRelayConnectionV11Options {
|
|
34
36
|
readonly requestPolicy: RemoteRequestPolicy;
|
|
@@ -51,6 +53,7 @@ export type RemoteRequestPolicy = (input: {
|
|
|
51
53
|
* a frozen Wire 1.0 path; callers must opt into this class to project typed Interactions.
|
|
52
54
|
*/
|
|
53
55
|
export declare class CompanionRelayBridgeV11 {
|
|
56
|
+
private readonly protocolVersion;
|
|
54
57
|
private readonly deviceId;
|
|
55
58
|
private readonly pairingToken;
|
|
56
59
|
private readonly workspaces;
|
|
@@ -120,3 +123,4 @@ export declare class OutboundCompanionRelayV11 {
|
|
|
120
123
|
private connect;
|
|
121
124
|
private scheduleReconnect;
|
|
122
125
|
}
|
|
126
|
+
export {};
|
package/dist/relay-bridge-v11.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createHash as e,randomUUID as t}from"node:crypto";import{RollRpcError as r}from"@roll-agent/client-node";import{jsonValueSchema as s}from"@roll-agent/protocol";import{RELAY_ERROR_CODES as n,RELAY_ERROR_CODES_V11 as o,RELAY_MESSAGE_TYPES_V11 as i,RELAY_REQUEST_METHODS_V11 as a,canonicalizeRelayJson as c,classifyRelayAck as d,getRelayErrorRetryability as p,getRelayErrorRetryabilityV11 as h,getRelayRequestMethodDispositionForVersion as u,parseRelayRequestParamsForVersion as l,parseRelayRequestResultForVersion as m,projectRelayOperationGetResultV11 as y,projectRelayThreadSnapshotV11 as R,relayEnvelopeIdSchema as g,relayMessageSchemaV11 as q,relayRuntimeRequestSchemaV11 as w}from"@roll-agent/relay-protocol";import{LocalApprovalDeniedError as f,LocalConfirmationRequiredError as I}from"./companion-workspace.js";import{materializeRelayFrameV11 as v}from"./relay-frame-buffer.js";class InvalidRelayRequestParamsV11Error extends Error{constructor(){super("Invalid Relay Wire 1.1 request params"),this.name="InvalidRelayRequestParamsV11Error"}}class LocalOnlyRelayRequestV11Error extends Error{constructor(e){super(`Relay method "${e}" is owned by the local Companion`),this.name="LocalOnlyRelayRequestV11Error"}}const k=1e4,C={code:n.encryptionRequired,message:"Encrypted Relay request required for this workspace",retryable:p(n.encryptionRequired)},E={code:o.remoteRequestDenied,message:"Remote request denied",retryable:h(o.remoteRequestDenied)};function b(t){const r={workspaceId:t.workspaceId,method:t.method,params:t.params};return e("sha256").update(c(r)).digest("hex")}function x(e,t){let r=t;return e.method===a.threadOpen||e.method===a.threadSnapshot?r=R(t):e.method===a.operationGet&&(r=y(t)),s.parse(m("1.1",e.method,r))}function M(e){if(e instanceof InvalidRelayRequestParamsV11Error)return{code:n.invalidParams,message:"Invalid Relay request params",retryable:p(n.invalidParams)};if(e instanceof r)return{code:e.data?.rollCode??`JSON_RPC_${String(e.code)}`,message:"Runtime request failed",retryable:e.data?.retryable??!1};if(e instanceof f||e instanceof I||e instanceof Error&&("LocalApprovalDeniedError"===e.name||"LocalConfirmationRequiredError"===e.name)){const t=e instanceof f||"LocalApprovalDeniedError"===e.name?n.localApprovalDenied:n.localConfirmationRequired;return{code:t,message:t===n.localApprovalDenied?"Local approval denied":"Local confirmation required",retryable:p(t)}}return{code:n.companionError,message:"Companion request failed",retryable:p(n.companionError)}}export class CompanionRelayBridgeV11{deviceId;pairingToken;workspaces;ciphers;maxRequestCacheEntries;acknowledged=new Map;inFlightRequestCache=new Map;settledRequestCache=new Map;releaseWorkspaceSubscriptions;generation;releaseTransportSubscriptions=[];closed=!1;constructor(e){if(this.deviceId=e.deviceId,this.pairingToken=e.pairingToken,this.workspaces=e.workspaces,this.ciphers=e.ciphers??new Map,this.maxRequestCacheEntries=e.maxRequestCacheEntries??k,!Number.isInteger(this.maxRequestCacheEntries)||this.maxRequestCacheEntries<1)throw new Error("maxRequestCacheEntries must be a positive integer");this.releaseWorkspaceSubscriptions=[...this.workspaces.entries()].map(([e,t])=>t.onBufferedRelayFrameV11(t=>{const r=this.generation;void 0!==r&&this.sendFrame(r,e,t)}))}connect(e,t){if(this.closed)throw new Error("Companion Relay Wire 1.1 bridge is closed");this.releaseCurrentGeneration(!0);const r={transport:e,requestPolicy:t.requestPolicy,responderPolicy:t.responderPolicy,responderContext:t.responderContext,controller:new AbortController,advertisedThrough:new Map,queue:Promise.resolve()};this.generation=r,this.releaseTransportSubscriptions=[e.onMessage(e=>{this.handleMessage(e,r).catch(()=>{})}),e.onClose(()=>{this.generation===r&&this.releaseCurrentGeneration()})],this.enqueue(r,{type:i.deviceConnect,protocolVersion:"1.1",deviceId:this.deviceId,pairingToken:this.pairingToken});for(const[e,t]of this.workspaces){const s=t.replayRelayFramesV11(this.acknowledged.get(e)??-1);void 0!==s.gap&&this.enqueue(r,{type:i.runtimeGap,workspaceId:e,fromRelaySequence:s.gap.fromRelaySequence,throughRelaySequence:s.gap.throughRelaySequence,recovery:"thread.snapshot"});for(const t of s.frames)this.sendFrame(r,e,t)}}close(){if(this.closed)return;this.closed=!0,this.releaseCurrentGeneration(!0);for(const e of this.releaseWorkspaceSubscriptions)e();const e=new Error("Companion Relay Wire 1.1 bridge closed");for(const t of this.workspaces.values())t.closeRemoteInteractions?.(e)}enqueue(e,t){this.enqueueTask(e,async()=>{await e.transport.send(t),this.generation===e&&this.markMessageAdvertised(e,t)})}enqueueTask(e,t){e.queue=e.queue.then(async()=>{this.generation===e&&await t()}).catch(()=>{this.failGeneration(e)})}failGeneration(e){if(this.generation===e){this.releaseCurrentGeneration();try{e.transport.close()}catch{}}}async handleMessage(e,t,r=!1){if(this.generation!==t)return;const s=q.safeParse(e);if(!s.success)return;const o=s.data;if(o.type===i.runtimeEncrypted){if("request"!==o.payloadKind||void 0===o.requestId)return;const e=this.ciphers.get(o.workspaceId);if(void 0===e)return;try{const r=w.parse(await e.decrypt(o));if(r.workspaceId!==o.workspaceId||r.requestId!==o.requestId)return;await this.handleMessage(r,t,!0)}catch{this.sendRuntimeResponse(t,{type:i.runtimeResponse,requestId:o.requestId,workspaceId:o.workspaceId,error:{code:n.encryptedPayloadInvalid,message:"Encrypted Relay payload could not be authenticated or parsed",retryable:p(n.encryptedPayloadInvalid)}})}return}if(o.type===i.runtimeAck){const e=this.workspaces.get(o.workspaceId);if(void 0===e)return;const r=this.acknowledged.get(o.workspaceId)??-1;return void("advance"===d({acknowledgedThrough:r,advertisedThrough:t.advertisedThrough.get(o.workspaceId)??-1,incomingThrough:o.throughRelaySequence})&&(this.acknowledged.set(o.workspaceId,o.throughRelaySequence),e.acknowledgeRelayFramesV11(o.throughRelaySequence)))}if(o.type!==i.runtimeRequest)return;if(!r&&this.ciphers.has(o.workspaceId))return void this.sendRuntimeResponse(t,{type:i.runtimeResponse,requestId:o.requestId,workspaceId:o.workspaceId,error:C});const a=await this.isRemoteRequestAllowed(o,t);if(this.generation!==t)return;if(!a)return void this.sendRuntimeResponse(t,{type:i.runtimeResponse,requestId:o.requestId,workspaceId:o.workspaceId,error:E});const c=this.workspaces.get(o.workspaceId);if(void 0===c)return void this.sendRuntimeResponse(t,{type:i.runtimeResponse,requestId:o.requestId,workspaceId:o.workspaceId,error:{code:n.workspaceNotFound,message:"The requested workspace is not paired with this Companion",retryable:p(n.workspaceNotFound)}});const h=await this.resolveRuntimeRequest(o,c,t);this.generation===t&&this.sendRuntimeResponse(t,h)}async isRemoteRequestAllowed(e,t){try{return await t.requestPolicy({workspaceId:e.workspaceId,requestId:e.requestId,method:e.method,responderContext:t.responderContext,signal:t.controller.signal})&&!t.controller.signal.aborted}catch{return!1}}resolveRuntimeRequest(e,t,r){const s=`${e.workspaceId}:${e.requestId}`,o=this.inFlightRequestCache.get(s),c=this.settledRequestCache.get(s),d=o??c;if(void 0!==d){const t=b(e);return d.fingerprint===t?(void 0!==c&&(this.settledRequestCache.delete(s),this.settledRequestCache.set(s,c)),d.response):Promise.resolve({type:i.runtimeResponse,requestId:e.requestId,workspaceId:e.workspaceId,error:{code:n.requestIdConflict,message:"Relay requestId was reused with different method or params",retryable:p(n.requestIdConflict)}})}if("mutation"!==u("1.1",e.method))return this.executeRuntimeRequest(e,t,r);const h={fingerprint:b(e),response:this.executeRuntimeRequest(e,t,r),generation:e.method===a.interactionCandidate?r:void 0};return this.inFlightRequestCache.set(s,h),h.response.then(()=>this.settleRequest(s,h),()=>this.settleRequest(s,h)).catch(()=>{}),h.response}async executeRuntimeRequest(e,t,r){try{if("local-only"===u("1.1",e.method))throw new LocalOnlyRelayRequestV11Error(e.method);let n;try{n=s.parse(l("1.1",e.method,e.params))}catch{throw new InvalidRelayRequestParamsV11Error}const o=w.parse({...e,params:n}),a={signal:r.controller.signal,responderPolicy:r.responderPolicy,responderContext:r.responderContext,workspaceId:e.workspaceId,requestId:e.requestId},c=await t.handleRemoteRequestV11(o,a);return{type:i.runtimeResponse,requestId:e.requestId,workspaceId:e.workspaceId,result:x(o,c)}}catch(t){return{type:i.runtimeResponse,requestId:e.requestId,workspaceId:e.workspaceId,error:M(t)}}}settleRequest(e,t){if(this.inFlightRequestCache.get(e)===t)for(this.inFlightRequestCache.delete(e),this.settledRequestCache.set(e,t);this.settledRequestCache.size>this.maxRequestCacheEntries;){const e=this.settledRequestCache.keys().next().value;if(void 0===e)return;this.settledRequestCache.delete(e)}}sendFrame(e,r,n){const o=v(r,n),a=this.ciphers.get(r);void 0!==a?this.enqueueTask(e,async()=>{const c=await a.encrypt(s.parse(o));if(this.generation!==e)return;const d=o.type===i.runtimeEvent?"event":"interaction";await e.transport.send({type:i.runtimeEncrypted,workspaceId:r,envelopeId:g.parse(t()),payloadKind:d,relaySequence:n.relaySequence,algorithm:a.algorithm,nonce:c.nonce,ciphertext:c.ciphertext}),this.generation===e&&this.markAdvertised(e,r,n.relaySequence)}):this.enqueue(e,o)}sendRuntimeResponse(e,r){const n=this.ciphers.get(r.workspaceId);void 0!==n?this.enqueueTask(e,async()=>{const o=await n.encrypt(s.parse(r));this.generation===e&&await e.transport.send({type:i.runtimeEncrypted,workspaceId:r.workspaceId,envelopeId:g.parse(t()),payloadKind:"response",requestId:r.requestId,algorithm:n.algorithm,nonce:o.nonce,ciphertext:o.ciphertext})}):this.enqueue(e,r)}releaseCurrentGeneration(e=!1){const t=this.generation;if(this.generation=void 0,t?.controller.abort(new Error("Relay transport generation ended")),void 0!==t)for(const[e,r]of this.inFlightRequestCache)r.generation===t&&this.inFlightRequestCache.delete(e);for(const e of this.releaseTransportSubscriptions)e();this.releaseTransportSubscriptions=[],e&&t?.transport.close()}markMessageAdvertised(e,t){t.type!==i.runtimeGap?t.type!==i.runtimeEvent&&t.type!==i.interactionRequest&&t.type!==i.interactionResolved&&t.type!==i.interactionCancelled||this.markAdvertised(e,t.workspaceId,t.relaySequence):this.markAdvertised(e,t.workspaceId,t.throughRelaySequence)}markAdvertised(e,t,r){e.advertisedThrough.set(t,Math.max(e.advertisedThrough.get(t)??-1,r))}}export function createWebSocketRelayTransportV11(e){return{send(t){e.send(JSON.stringify(t))},onMessage(t){const r=e=>{if("string"==typeof e.data)try{t(JSON.parse(e.data))}catch{}};return e.addEventListener("message",r),()=>e.removeEventListener("message",r)},onClose:t=>(e.addEventListener("close",t),()=>e.removeEventListener("close",t)),close(){e.close()}}}export class OutboundCompanionRelayV11{bridge;connectTransport;minReconnectMs;maxReconnectMs;reconnectMs;timer;transport;stopped=!0;connecting=!1;constructor(e){this.bridge=e.bridge,this.connectTransport=e.connectTransport,this.minReconnectMs=e.minReconnectMs??500,this.maxReconnectMs=e.maxReconnectMs??3e4,this.reconnectMs=this.minReconnectMs}start(){this.stopped&&(this.stopped=!1,this.connect().catch(()=>{}))}stop(){this.stopped=!0,void 0!==this.timer&&(clearTimeout(this.timer),this.timer=void 0),this.transport?.close(),this.transport=void 0,this.bridge.close()}async connect(){if(!this.stopped&&!this.connecting){this.connecting=!0;try{const e=await this.connectTransport();if(this.stopped)return void e.transport.close();this.transport=e.transport,this.reconnectMs=this.minReconnectMs,e.transport.onClose(()=>{this.transport===e.transport&&(this.transport=void 0,this.scheduleReconnect())}),this.bridge.connect(e.transport,{requestPolicy:e.requestPolicy,responderPolicy:e.responderPolicy,responderContext:e.responderContext})}catch{this.scheduleReconnect()}finally{this.connecting=!1}}}scheduleReconnect(){if(this.stopped||void 0!==this.timer)return;const e=this.reconnectMs;this.reconnectMs=Math.min(2*this.reconnectMs,this.maxReconnectMs),this.timer=setTimeout(()=>{this.timer=void 0,this.connect().catch(()=>{})},e)}}
|
|
1
|
+
import{createHash as e,randomUUID as t}from"node:crypto";import{RollRpcError as r}from"@roll-agent/client-node";import{jsonValueSchema as s}from"@roll-agent/protocol";import{RELAY_ERROR_CODES as o,RELAY_ERROR_CODES_V11 as n,RELAY_MESSAGE_TYPES_V11 as i,RELAY_REQUEST_METHODS_V11 as a,canonicalizeRelayJson as c,classifyRelayAck as d,getRelayErrorRetryability as p,getRelayErrorRetryabilityV11 as h,getRelayRequestMethodDispositionForVersion as u,parseRelayRequestParamsForVersion as l,parseRelayRequestResultForVersion as m,projectRelayOperationGetResultV11 as y,projectRelayThreadSnapshotV11 as R,relayEnvelopeIdSchema as g,relayMessageSchemaV11 as q,relayMessageSchemaV12 as w,relayRuntimeRequestSchemaV12 as f,projectRelayThreadSnapshotV12 as I,projectRelayOperationGetResultV12 as v,projectRelayMessageV12ToV11 as k,projectRelayCapabilitiesV11 as C}from"@roll-agent/relay-protocol";import{LocalApprovalDeniedError as E,LocalConfirmationRequiredError as b}from"./companion-workspace.js";import{materializeRelayFrameV12 as x}from"./relay-frame-buffer.js";class InvalidRelayRequestParamsV11Error extends Error{constructor(){super("Invalid Relay Wire 1.1 request params"),this.name="InvalidRelayRequestParamsV11Error"}}class LocalOnlyRelayRequestV11Error extends Error{constructor(e){super(`Relay method "${e}" is owned by the local Companion`),this.name="LocalOnlyRelayRequestV11Error"}}const M=1e4,T={code:o.encryptionRequired,message:"Encrypted Relay request required for this workspace",retryable:p(o.encryptionRequired)},S={code:n.remoteRequestDenied,message:"Remote request denied",retryable:h(n.remoteRequestDenied)};function V(t){const r={workspaceId:t.workspaceId,method:t.method,params:t.params};return e("sha256").update(c(r)).digest("hex")}function P(e,t,r){let o=t;return e.method===a.threadOpen||e.method===a.threadSnapshot?o="1.2"===r?I(t):R(t):e.method===a.operationGet&&(o="1.2"===r?v(t):y(t)),"1.1"===r&&"thread.capabilities"===e.method&&(o=C(o)),s.parse(m(r,e.method,o))}function F(e){if(e instanceof InvalidRelayRequestParamsV11Error)return{code:o.invalidParams,message:"Invalid Relay request params",retryable:p(o.invalidParams)};if(e instanceof r)return{code:e.data?.rollCode??`JSON_RPC_${String(e.code)}`,message:"Runtime request failed",retryable:e.data?.retryable??!1};if(e instanceof E||e instanceof b||e instanceof Error&&("LocalApprovalDeniedError"===e.name||"LocalConfirmationRequiredError"===e.name)){const t=e instanceof E||"LocalApprovalDeniedError"===e.name?o.localApprovalDenied:o.localConfirmationRequired;return{code:t,message:t===o.localApprovalDenied?"Local approval denied":"Local confirmation required",retryable:p(t)}}return{code:o.companionError,message:"Companion request failed",retryable:p(o.companionError)}}export class CompanionRelayBridgeV11{protocolVersion;deviceId;pairingToken;workspaces;ciphers;maxRequestCacheEntries;acknowledged=new Map;inFlightRequestCache=new Map;settledRequestCache=new Map;releaseWorkspaceSubscriptions;generation;releaseTransportSubscriptions=[];closed=!1;constructor(e){if(this.protocolVersion=e.protocolVersion??"1.1",this.deviceId=e.deviceId,this.pairingToken=e.pairingToken,this.workspaces=e.workspaces,this.ciphers=e.ciphers??new Map,this.maxRequestCacheEntries=e.maxRequestCacheEntries??M,!Number.isInteger(this.maxRequestCacheEntries)||this.maxRequestCacheEntries<1)throw new Error("maxRequestCacheEntries must be a positive integer");this.releaseWorkspaceSubscriptions=[...this.workspaces.entries()].map(([e,t])=>t.onBufferedRelayFrameV11(t=>{const r=this.generation;void 0!==r&&this.sendFrame(r,e,t)}))}connect(e,t){if(this.closed)throw new Error("Companion Relay Wire 1.1 bridge is closed");this.releaseCurrentGeneration(!0);const r={transport:e,requestPolicy:t.requestPolicy,responderPolicy:t.responderPolicy,responderContext:t.responderContext,controller:new AbortController,advertisedThrough:new Map,queue:Promise.resolve()};this.generation=r,this.releaseTransportSubscriptions=[e.onMessage(e=>{this.handleMessage(e,r).catch(()=>{})}),e.onClose(()=>{this.generation===r&&this.releaseCurrentGeneration()})],this.enqueue(r,{type:i.deviceConnect,protocolVersion:this.protocolVersion,deviceId:this.deviceId,pairingToken:this.pairingToken});for(const[e,t]of this.workspaces){const s=t.replayRelayFramesV11(this.acknowledged.get(e)??-1);void 0!==s.gap&&this.enqueue(r,{type:i.runtimeGap,workspaceId:e,fromRelaySequence:s.gap.fromRelaySequence,throughRelaySequence:s.gap.throughRelaySequence,recovery:"thread.snapshot"});for(const t of s.frames)this.sendFrame(r,e,t)}}close(){if(this.closed)return;this.closed=!0,this.releaseCurrentGeneration(!0);for(const e of this.releaseWorkspaceSubscriptions)e();const e=new Error("Companion Relay Wire 1.1 bridge closed");for(const t of this.workspaces.values())t.closeRemoteInteractions?.(e)}enqueue(e,t){Buffer.byteLength(JSON.stringify(t),"utf8")>1048576?e.transport.close():this.enqueueTask(e,async()=>{await e.transport.send(t),this.generation===e&&this.markMessageAdvertised(e,t)})}enqueueTask(e,t){e.queue=e.queue.then(async()=>{this.generation===e&&await t()}).catch(()=>{this.failGeneration(e)})}failGeneration(e){if(this.generation===e){this.releaseCurrentGeneration();try{e.transport.close()}catch{}}}async handleMessage(e,t,r=!1){if(this.generation!==t)return;const s=("1.2"===this.protocolVersion?w:q).safeParse(e);if(!s.success)return;const n=s.data;if(n.type===i.runtimeEncrypted){if("request"!==n.payloadKind||void 0===n.requestId)return;const e=this.ciphers.get(n.workspaceId);if(void 0===e)return;try{const r=f.parse(await e.decrypt(n));if(r.workspaceId!==n.workspaceId||r.requestId!==n.requestId)return;await this.handleMessage(r,t,!0)}catch{this.sendRuntimeResponse(t,{type:i.runtimeResponse,requestId:n.requestId,workspaceId:n.workspaceId,error:{code:o.encryptedPayloadInvalid,message:"Encrypted Relay payload could not be authenticated or parsed",retryable:p(o.encryptedPayloadInvalid)}})}return}if(n.type===i.runtimeAck){const e=this.workspaces.get(n.workspaceId);if(void 0===e)return;const r=this.acknowledged.get(n.workspaceId)??-1;return void("advance"===d({acknowledgedThrough:r,advertisedThrough:t.advertisedThrough.get(n.workspaceId)??-1,incomingThrough:n.throughRelaySequence})&&(this.acknowledged.set(n.workspaceId,n.throughRelaySequence),e.acknowledgeRelayFramesV11(n.throughRelaySequence)))}if(n.type!==i.runtimeRequest)return;if(!r&&this.ciphers.has(n.workspaceId))return void this.sendRuntimeResponse(t,{type:i.runtimeResponse,requestId:n.requestId,workspaceId:n.workspaceId,error:T});const a=await this.isRemoteRequestAllowed(n,t);if(this.generation!==t)return;if(!a)return void this.sendRuntimeResponse(t,{type:i.runtimeResponse,requestId:n.requestId,workspaceId:n.workspaceId,error:S});const c=this.workspaces.get(n.workspaceId);if(void 0===c)return void this.sendRuntimeResponse(t,{type:i.runtimeResponse,requestId:n.requestId,workspaceId:n.workspaceId,error:{code:o.workspaceNotFound,message:"The requested workspace is not paired with this Companion",retryable:p(o.workspaceNotFound)}});const h=await this.resolveRuntimeRequest(n,c,t);this.generation===t&&this.sendRuntimeResponse(t,h)}async isRemoteRequestAllowed(e,t){try{return await t.requestPolicy({workspaceId:e.workspaceId,requestId:e.requestId,method:e.method,responderContext:t.responderContext,signal:t.controller.signal})&&!t.controller.signal.aborted}catch{return!1}}resolveRuntimeRequest(e,t,r){const s=`${e.workspaceId}:${e.requestId}`,n=this.inFlightRequestCache.get(s),c=this.settledRequestCache.get(s),d=n??c;if(void 0!==d){const t=V(e);return d.fingerprint===t?(void 0!==c&&(this.settledRequestCache.delete(s),this.settledRequestCache.set(s,c)),d.response):Promise.resolve({type:i.runtimeResponse,requestId:e.requestId,workspaceId:e.workspaceId,error:{code:o.requestIdConflict,message:"Relay requestId was reused with different method or params",retryable:p(o.requestIdConflict)}})}if("mutation"!==u(this.protocolVersion,e.method))return this.executeRuntimeRequest(e,t,r);const h={fingerprint:V(e),response:this.executeRuntimeRequest(e,t,r),generation:e.method===a.interactionCandidate?r:void 0};return this.inFlightRequestCache.set(s,h),h.response.then(()=>this.settleRequest(s,h),()=>this.settleRequest(s,h)).catch(()=>{}),h.response}async executeRuntimeRequest(e,t,r){try{if("local-only"===u(this.protocolVersion,e.method))throw new LocalOnlyRelayRequestV11Error(e.method);let o;try{o=s.parse(l(this.protocolVersion,e.method,e.params))}catch{throw new InvalidRelayRequestParamsV11Error}const n=f.parse({...e,params:o}),a={signal:r.controller.signal,responderPolicy:r.responderPolicy,responderContext:r.responderContext,workspaceId:e.workspaceId,requestId:e.requestId},c=await t.handleRemoteRequestV11(n,a);return{type:i.runtimeResponse,requestId:e.requestId,workspaceId:e.workspaceId,result:P(n,c,this.protocolVersion)}}catch(t){return{type:i.runtimeResponse,requestId:e.requestId,workspaceId:e.workspaceId,error:F(t)}}}settleRequest(e,t){if(this.inFlightRequestCache.get(e)===t)for(this.inFlightRequestCache.delete(e),this.settledRequestCache.set(e,t);this.settledRequestCache.size>this.maxRequestCacheEntries;){const e=this.settledRequestCache.keys().next().value;if(void 0===e)return;this.settledRequestCache.delete(e)}}sendFrame(e,r,o){const n=x(r,o),a="1.2"===this.protocolVersion?n:k(n),c=this.ciphers.get(r);void 0!==c?this.enqueueTask(e,async()=>{const n=await c.encrypt(s.parse(a));if(this.generation!==e)return;const d=a.type===i.runtimeEvent?"event":"interaction";await e.transport.send({type:i.runtimeEncrypted,workspaceId:r,envelopeId:g.parse(t()),payloadKind:d,relaySequence:o.relaySequence,algorithm:c.algorithm,nonce:n.nonce,ciphertext:n.ciphertext}),this.generation===e&&this.markAdvertised(e,r,o.relaySequence)}):this.enqueue(e,a)}sendRuntimeResponse(e,r){const o=this.ciphers.get(r.workspaceId);void 0!==o?this.enqueueTask(e,async()=>{const n=await o.encrypt(s.parse(r));this.generation===e&&await e.transport.send({type:i.runtimeEncrypted,workspaceId:r.workspaceId,envelopeId:g.parse(t()),payloadKind:"response",requestId:r.requestId,algorithm:o.algorithm,nonce:n.nonce,ciphertext:n.ciphertext})}):this.enqueue(e,r)}releaseCurrentGeneration(e=!1){const t=this.generation;if(this.generation=void 0,t?.controller.abort(new Error("Relay transport generation ended")),void 0!==t)for(const[e,r]of this.inFlightRequestCache)r.generation===t&&this.inFlightRequestCache.delete(e);for(const e of this.releaseTransportSubscriptions)e();this.releaseTransportSubscriptions=[],e&&t?.transport.close()}markMessageAdvertised(e,t){t.type!==i.runtimeGap?t.type!==i.runtimeEvent&&t.type!==i.interactionRequest&&t.type!==i.interactionResolved&&t.type!==i.interactionCancelled||this.markAdvertised(e,t.workspaceId,t.relaySequence):this.markAdvertised(e,t.workspaceId,t.throughRelaySequence)}markAdvertised(e,t,r){e.advertisedThrough.set(t,Math.max(e.advertisedThrough.get(t)??-1,r))}}export function createWebSocketRelayTransportV11(e){return{send(t){e.send(JSON.stringify(t))},onMessage(t){const r=e=>{if("string"==typeof e.data)try{t(JSON.parse(e.data))}catch{}};return e.addEventListener("message",r),()=>e.removeEventListener("message",r)},onClose:t=>(e.addEventListener("close",t),()=>e.removeEventListener("close",t)),close(){e.close()}}}export class OutboundCompanionRelayV11{bridge;connectTransport;minReconnectMs;maxReconnectMs;reconnectMs;timer;transport;stopped=!0;connecting=!1;constructor(e){this.bridge=e.bridge,this.connectTransport=e.connectTransport,this.minReconnectMs=e.minReconnectMs??500,this.maxReconnectMs=e.maxReconnectMs??3e4,this.reconnectMs=this.minReconnectMs}start(){this.stopped&&(this.stopped=!1,this.connect().catch(()=>{}))}stop(){this.stopped=!0,void 0!==this.timer&&(clearTimeout(this.timer),this.timer=void 0),this.transport?.close(),this.transport=void 0,this.bridge.close()}async connect(){if(!this.stopped&&!this.connecting){this.connecting=!0;try{const e=await this.connectTransport();if(this.stopped)return void e.transport.close();this.transport=e.transport,this.reconnectMs=this.minReconnectMs,e.transport.onClose(()=>{this.transport===e.transport&&(this.transport=void 0,this.scheduleReconnect())}),this.bridge.connect(e.transport,{requestPolicy:e.requestPolicy,responderPolicy:e.responderPolicy,responderContext:e.responderContext})}catch{this.scheduleReconnect()}finally{this.connecting=!1}}}scheduleReconnect(){if(this.stopped||void 0!==this.timer)return;const e=this.reconnectMs;this.reconnectMs=Math.min(2*this.reconnectMs,this.maxReconnectMs),this.timer=setTimeout(()=>{this.timer=void 0,this.connect().catch(()=>{})},e)}}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type RuntimeEventEnvelope } from "@roll-agent/protocol";
|
|
2
|
-
import { type RelayMessageV11, type RelayRuntimeEventEnvelopeV11, type WorkspaceId } from "@roll-agent/relay-protocol";
|
|
2
|
+
import { type RelayMessageV11 as LegacyRelayMessageV11, type RelayMessageV12 as RelayMessageV11, type RelayRuntimeEventEnvelopeV12 as RelayRuntimeEventEnvelopeV11, type WorkspaceId } from "@roll-agent/relay-protocol";
|
|
3
3
|
import type { CompanionInteractionFrameDraftV11 } from "./interaction-broker.ts";
|
|
4
4
|
export declare const DEFAULT_COMPANION_RELAY_FRAME_MAX_EVENTS = 10000;
|
|
5
5
|
export declare const DEFAULT_COMPANION_RELAY_FRAME_MAX_BYTES: number;
|
|
@@ -28,8 +28,10 @@ export interface CompanionRelayFrameBufferOptions {
|
|
|
28
28
|
readonly maxEvents?: number;
|
|
29
29
|
readonly maxBytes?: number;
|
|
30
30
|
}
|
|
31
|
-
/** Materializes and strictly validates
|
|
32
|
-
export declare function materializeRelayFrameV11(workspaceId: WorkspaceId, entry: CompanionRelayFrameEntryV11):
|
|
31
|
+
/** Materializes and strictly validates the frozen Wire 1.1 projection. */
|
|
32
|
+
export declare function materializeRelayFrameV11(workspaceId: WorkspaceId, entry: CompanionRelayFrameEntryV11): LegacyRelayMessageV11;
|
|
33
|
+
/** Materializes Wire 1.2 metadata without embedding business data. */
|
|
34
|
+
export declare function materializeRelayFrameV12(workspaceId: WorkspaceId, entry: CompanionRelayFrameEntryV11): RelayMessageV11;
|
|
33
35
|
/**
|
|
34
36
|
* Per-workspace in-memory replay buffer for Wire 1.1 runtime and interaction frames.
|
|
35
37
|
* It is intentionally independent from the frozen Wire 1.0 `CompanionEventBuffer`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{RUNTIME_V13_DEFAULT_REPLAY_BUFFER_BYTES as e}from"@roll-agent/protocol";import{
|
|
1
|
+
import{RUNTIME_V13_DEFAULT_REPLAY_BUFFER_BYTES as e}from"@roll-agent/protocol";import{projectRuntimeEventEnvelopeForRelayV12 as t,projectRelayMessageV12ToV11 as r,relayInteractionCancelledSchemaV11 as n,relayInteractionRequestSchemaV11 as s,relayInteractionResolvedSchemaV11 as i,relayRuntimeEventSchemaV12 as a,workspaceIdSchema as o}from"@roll-agent/relay-protocol";export const DEFAULT_COMPANION_RELAY_FRAME_MAX_EVENTS=1e4;export const DEFAULT_COMPANION_RELAY_FRAME_MAX_BYTES=e;const u=o.parse("00000000-0000-4000-8000-000000000000");function c(e){if("workspaceId"in e||"relaySequence"in e)throw new Error("Buffered interaction drafts must not contain workspace routing fields");const t={...e,workspaceId:u,relaySequence:0};if("interaction.request"===e.type){const{workspaceId:e,relaySequence:r,...n}=s.parse(t);return n}if("interaction.resolved"===e.type){const{workspaceId:e,relaySequence:r,...n}=i.parse(t);return n}const{workspaceId:r,relaySequence:a,...o}=n.parse(t);return o}export function materializeRelayFrameV11(e,t){return r(materializeRelayFrameV12(e,t))}export function materializeRelayFrameV12(e,t){return"runtime.event"===t.type?a.parse({...t,workspaceId:e}):"interaction.request"===t.type?s.parse({...t,workspaceId:e}):"interaction.resolved"===t.type?i.parse({...t,workspaceId:e}):n.parse({...t,workspaceId:e})}export class CompanionRelayFrameBuffer{maxEvents;maxBytes;entries=[];nextSequence=0;retainedBytes=0;droppedThroughSequence=-1;constructor(e={}){if(this.maxEvents=e.maxEvents??1e4,this.maxBytes=e.maxBytes??DEFAULT_COMPANION_RELAY_FRAME_MAX_BYTES,!Number.isInteger(this.maxEvents)||this.maxEvents<1)throw new Error("maxEvents must be a positive integer");if(!Number.isInteger(this.maxBytes)||this.maxBytes<1)throw new Error("maxBytes must be a positive integer")}appendRuntimeEvent(e){const r=t(e);if(void 0!==r)return this.append({type:"runtime.event",relaySequence:this.nextSequence,event:r})}appendInteraction(e){const t=c(e);return this.append({...t,relaySequence:this.nextSequence})}replay(e=-1){if(!Number.isInteger(e)||e<-1)throw new Error("afterRelaySequence must be an integer greater than or equal to -1");const t=e<this.droppedThroughSequence?{fromRelaySequence:e+1,throughRelaySequence:this.droppedThroughSequence}:void 0;return{frames:this.entries.filter(t=>t.frame.relaySequence>e).map(e=>e.frame),gap:t}}acknowledge(e){if(!Number.isInteger(e)||e<-1)throw new Error("throughRelaySequence must be an integer greater than or equal to -1");if(!(e>this.highestRelaySequence))for(;void 0!==this.entries[0]&&this.entries[0].frame.relaySequence<=e;)this.dropOldest()}get size(){return this.entries.length}get bytes(){return this.retainedBytes}get highestRelaySequence(){return this.nextSequence-1}append(e){const t={frame:e,bytes:Buffer.byteLength(JSON.stringify(e),"utf8")};return this.nextSequence+=1,this.entries.push(t),this.retainedBytes+=t.bytes,this.enforceLimits(),e}enforceLimits(){for(;this.entries.length>this.maxEvents||this.retainedBytes>this.maxBytes&&this.entries.length>0;)this.dropOldest()}dropOldest(){const e=this.entries.shift();void 0!==e&&(this.retainedBytes-=e.bytes,this.droppedThroughSequence=Math.max(this.droppedThroughSequence,e.frame.relaySequence))}}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/** Producer metadata is never a grant: recompute host authorization for every query. */
|
|
2
|
+
export declare function projectRemoteAppOutputCapabilities(value: unknown, supported: boolean, policy: (agentName: string, toolName: string) => boolean | Promise<boolean>): Promise<unknown>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{jsonValueSchema as t}from"@roll-agent/protocol";function e(t){return"object"==typeof t&&null!=t&&!Array.isArray(t)}export async function projectRemoteAppOutputCapabilities(r,a,o){const p=t.parse(r);if(!e(p)||!e(p.manifest))return p;const n=p.manifest;if(!e(n))return p;const u=n.tools;if(!Array.isArray(u))return{...p,manifest:{...n,appOutput:{supported:a}}};const i=await Promise.all(u.map(async t=>{if(!e(t)||!e(t.appOutput))return t;const r=t.appOutput;if(!e(r))return t;let p=!1;if(a&&!0===r.remoteReadable&&"string"==typeof t.agentName&&"string"==typeof t.toolName)try{p=await o(t.agentName,t.toolName)}catch{}return{...t,appOutput:{...r,remoteReadable:p}}}));return{...p,manifest:{...n,tools:i,appOutput:{supported:a}}}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@roll-agent/companion",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -30,10 +30,10 @@
|
|
|
30
30
|
"node": ">=22.6.0"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"
|
|
34
|
-
"@roll-agent/
|
|
35
|
-
"@roll-agent/protocol": "0.
|
|
36
|
-
"
|
|
33
|
+
"@roll-agent/client-node": "0.6.0",
|
|
34
|
+
"@roll-agent/protocol": "0.7.0",
|
|
35
|
+
"@roll-agent/relay-protocol": "0.4.0",
|
|
36
|
+
"zod": "3.25.76"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/node": "^22.0.0"
|