1- import { sendCommand } from '@metamask/kernel-node-runtime/daemon' ;
2- import { isJsonRpcFailure } from '@metamask/utils' ;
1+ import type { ParsedInvocation } from '@metamask/kernel-utils/session/provision' ;
2+ import type { JsonRpcResponse } from '@metamask/utils' ;
3+ import { assertIsJsonRpcResponse , isJsonRpcFailure } from '@metamask/utils' ;
4+ import { randomUUID } from 'node:crypto' ;
5+ import { createConnection } from 'node:net' ;
6+ import type { Socket } from 'node:net' ;
37
48import type { CapData , Decision } from './types.ts' ;
59
6- export { sendCommand } ;
10+ // ─── Minimal socket-RPC client (no @endo dependencies) ───────────────────────
11+
12+ /**
13+ * Options for {@link sendCommand}.
14+ */
15+ export type SendCommandOptions = {
16+ /** The UNIX socket path. */
17+ socketPath : string ;
18+ /** The RPC method name. */
19+ method : string ;
20+ /** Optional method parameters. */
21+ params ?: Record < string , unknown > | unknown [ ] | undefined ;
22+ /** Read timeout in milliseconds (default: no timeout). */
23+ timeoutMs ?: number | undefined ;
24+ } ;
25+
26+ /**
27+ * @param socketPath - The socket path to connect to.
28+ * @returns A connected socket.
29+ */
30+ async function connectSocket ( socketPath : string ) : Promise < Socket > {
31+ return new Promise ( ( resolve , reject ) => {
32+ const socket = createConnection ( socketPath , ( ) => {
33+ socket . removeListener ( 'error' , reject ) ;
34+ resolve ( socket ) ;
35+ } ) ;
36+ socket . on ( 'error' , reject ) ;
37+ } ) ;
38+ }
39+
40+ /**
41+ * @param socket - The socket to write to.
42+ * @param line - The line to write (without trailing newline).
43+ */
44+ async function writeLine ( socket : Socket , line : string ) : Promise < void > {
45+ return new Promise ( ( resolve , reject ) => {
46+ socket . write ( `${ line } \n` , ( error ) => {
47+ if ( error ) {
48+ reject ( error ) ;
49+ } else {
50+ resolve ( ) ;
51+ }
52+ } ) ;
53+ } ) ;
54+ }
55+
56+ /**
57+ * @param socket - The socket to read from.
58+ * @param timeoutMs - Optional timeout in milliseconds.
59+ * @returns The line read (without trailing newline).
60+ */
61+ async function readLine ( socket : Socket , timeoutMs ?: number ) : Promise < string > {
62+ return new Promise ( ( resolve , reject ) => {
63+ let buffer = '' ;
64+ let timer : ReturnType < typeof setTimeout > | undefined ;
65+
66+ if ( timeoutMs !== undefined ) {
67+ timer = setTimeout ( ( ) => {
68+ cleanup ( ) ;
69+ reject ( new Error ( 'Socket read timed out' ) ) ;
70+ } , timeoutMs ) ;
71+ }
72+
73+ const onData = ( data : Buffer ) : void => {
74+ buffer += data . toString ( ) ;
75+ const idx = buffer . indexOf ( '\n' ) ;
76+ if ( idx !== - 1 ) {
77+ cleanup ( ) ;
78+ resolve ( buffer . slice ( 0 , idx ) ) ;
79+ }
80+ } ;
81+
82+ const onError = ( error : Error ) : void => {
83+ cleanup ( ) ;
84+ reject ( error ) ;
85+ } ;
86+
87+ const onEnd = ( ) : void => {
88+ cleanup ( ) ;
89+ reject ( new Error ( 'Socket closed before response received' ) ) ;
90+ } ;
91+
92+ const onClose = ( ) : void => {
93+ cleanup ( ) ;
94+ reject ( new Error ( 'Socket closed before response received' ) ) ;
95+ } ;
96+
97+ /** Remove listeners registered by this call and clear the timeout. */
98+ function cleanup ( ) : void {
99+ if ( timer !== undefined ) {
100+ clearTimeout ( timer ) ;
101+ }
102+ socket . removeListener ( 'data' , onData ) ;
103+ socket . removeListener ( 'error' , onError ) ;
104+ socket . removeListener ( 'end' , onEnd ) ;
105+ socket . removeListener ( 'close' , onClose ) ;
106+ }
107+
108+ socket . on ( 'data' , onData ) ;
109+ socket . once ( 'error' , onError ) ;
110+ socket . once ( 'end' , onEnd ) ;
111+ socket . once ( 'close' , onClose ) ;
112+ } ) ;
113+ }
114+
115+ /**
116+ * Send a JSON-RPC request to the daemon over a UNIX socket and return the response.
117+ *
118+ * Opens a connection, writes one JSON-RPC request line, reads one JSON-RPC
119+ * response line, then closes the connection. Retries once after a short delay
120+ * if the connection is rejected.
121+ *
122+ * @param options - Command options.
123+ * @param options.socketPath - The UNIX socket path.
124+ * @param options.method - The RPC method name.
125+ * @param options.params - Optional method parameters.
126+ * @param options.timeoutMs - Read timeout in milliseconds (default: no timeout).
127+ * @returns The parsed JSON-RPC response.
128+ */
129+ export async function sendCommand ( {
130+ socketPath,
131+ method,
132+ params,
133+ timeoutMs,
134+ } : SendCommandOptions ) : Promise < JsonRpcResponse > {
135+ const id = randomUUID ( ) ;
136+ const request = {
137+ jsonrpc : '2.0' ,
138+ id,
139+ method,
140+ ...( params === undefined ? { } : { params } ) ,
141+ } ;
142+
143+ const attempt = async ( ) : Promise < JsonRpcResponse > => {
144+ const socket = await connectSocket ( socketPath ) ;
145+ try {
146+ await writeLine ( socket , JSON . stringify ( request ) ) ;
147+ const responseLine = await readLine ( socket , timeoutMs ) ;
148+ const parsed : unknown = JSON . parse ( responseLine ) ;
149+ assertIsJsonRpcResponse ( parsed ) ;
150+ return parsed ;
151+ } finally {
152+ socket . destroy ( ) ;
153+ }
154+ } ;
155+
156+ try {
157+ return await attempt ( ) ;
158+ } catch ( error : unknown ) {
159+ const code = ( error as NodeJS . ErrnoException | undefined ) ?. code ;
160+ if ( code !== 'ECONNREFUSED' && code !== 'ECONNRESET' ) {
161+ throw error ;
162+ }
163+ await new Promise ( ( resolve ) => setTimeout ( resolve , 100 ) ) ;
164+ return attempt ( ) ;
165+ }
166+ }
167+
168+ // ─── RPC helpers ──────────────────────────────────────────────────────────────
7169
8170/**
9171 * Check whether the daemon is running.
@@ -56,16 +218,21 @@ export async function createKernelSession(
56218 * @param socketPath - The UNIX socket path.
57219 * @param kernelSessionId - The kernel session to route the request through.
58220 * @param description - Human-readable description of the requested operation.
59- * @param options - Optional reason and client-side timeout .
221+ * @param options - Optional request metadata .
60222 * @param options.reason - Optional reason for the request.
61223 * @param options.timeoutMs - Optional client-side timeout in milliseconds.
224+ * @param options.invocations - Parsed invocations to forward to the TUI for the provision editor.
62225 * @returns The TUI's decision.
63226 */
64227export async function authorizeRequest (
65228 socketPath : string ,
66229 kernelSessionId : string ,
67230 description : string ,
68- options ?: { reason ?: string ; timeoutMs ?: number } ,
231+ options ?: {
232+ reason ?: string ;
233+ timeoutMs ?: number ;
234+ invocations ?: ParsedInvocation [ ] ;
235+ } ,
69236) : Promise < Decision > {
70237 const params : Record < string , unknown > = {
71238 sessionId : kernelSessionId ,
@@ -77,6 +244,9 @@ export async function authorizeRequest(
77244 if ( options ?. timeoutMs !== undefined ) {
78245 params . timeoutMs = options . timeoutMs ;
79246 }
247+ if ( options ?. invocations !== undefined ) {
248+ params . invocations = options . invocations ;
249+ }
80250 const response = await sendCommand ( {
81251 socketPath,
82252 method : 'session.authorize' ,
0 commit comments