@@ -24,17 +24,32 @@ import {
2424} from "./security.mjs" ;
2525import {
2626 canonicalizeEnvelope ,
27- canonicalizeOutbound ,
27+ canonicalizeProtocolRequest ,
2828 canonicalizeSendRequest ,
2929} from "./crypto.mjs" ;
3030
3131const SOCKET_DIR = path . join ( homedir ( ) , ".pi" , "session-control" ) ;
3232const AGENT_TIMEOUT_MS = 120_000 ;
33- const API_PORT = parseInt ( process . env . BRIDGE_API_PORT || "7890" , 10 ) ;
34- const POLL_INTERVAL_MS = parseInt ( process . env . SLACK_BROKER_POLL_INTERVAL_MS || "3000" , 10 ) ;
35- const MAX_MESSAGES = parseInt ( process . env . SLACK_BROKER_MAX_MESSAGES || "10" , 10 ) ;
36- const DEDUPE_TTL_MS = parseInt ( process . env . SLACK_BROKER_DEDUPE_TTL_MS || String ( 20 * 60 * 1000 ) , 10 ) ;
33+
34+ function clampInt ( value , min , max , fallback ) {
35+ const parsed = Number . parseInt ( String ( value ?? "" ) , 10 ) ;
36+ if ( ! Number . isFinite ( parsed ) ) return fallback ;
37+ return Math . min ( max , Math . max ( min , parsed ) ) ;
38+ }
39+
40+ const API_PORT = clampInt ( process . env . BRIDGE_API_PORT || "7890" , 0 , 65535 , 7890 ) ;
41+ const POLL_INTERVAL_MS = clampInt ( process . env . SLACK_BROKER_POLL_INTERVAL_MS || "3000" , 0 , 60_000 , 3000 ) ;
42+ const MAX_MESSAGES = clampInt ( process . env . SLACK_BROKER_MAX_MESSAGES || "10" , 1 , 100 , 10 ) ;
43+ const MAX_WAIT_SECONDS = 25 ;
44+ const BROKER_WAIT_SECONDS = clampInt ( process . env . SLACK_BROKER_WAIT_SECONDS || "20" , 0 , MAX_WAIT_SECONDS , 20 ) ;
45+ const DEDUPE_TTL_MS = clampInt (
46+ process . env . SLACK_BROKER_DEDUPE_TTL_MS || String ( 20 * 60 * 1000 ) ,
47+ 1_000 ,
48+ 7 * 24 * 60 * 60 * 1000 ,
49+ 20 * 60 * 1000 ,
50+ ) ;
3751const MAX_BACKOFF_MS = 30_000 ;
52+ const INBOX_PROTOCOL_VERSION = "2026-02-1" ;
3853const BROKER_HEALTH_PATH = path . join ( homedir ( ) , ".pi" , "agent" , "broker-health.json" ) ;
3954
4055function ts ( ) {
@@ -351,12 +366,25 @@ function getThreadId(channel, threadTs) {
351366 return id ;
352367}
353368
354- function signRequest ( action , timestamp , payloadField ) {
355- const canonical = canonicalizeOutbound ( workspaceId , action , timestamp , payloadField ) ;
369+ function signProtocolRequest ( action , timestamp , payload ) {
370+ const canonical = canonicalizeProtocolRequest (
371+ workspaceId ,
372+ INBOX_PROTOCOL_VERSION ,
373+ action ,
374+ timestamp ,
375+ payload ,
376+ ) ;
356377 const sig = sodium . crypto_sign_detached ( canonical , cryptoState . serverSignSecretKey ) ;
357378 return toBase64 ( sig ) ;
358379}
359380
381+ function signPullRequest ( timestamp , maxMessages , waitSeconds ) {
382+ return signProtocolRequest ( "inbox.pull" , timestamp , {
383+ max_messages : maxMessages ,
384+ wait_seconds : waitSeconds ,
385+ } ) ;
386+ }
387+
360388async function brokerFetch ( pathname , body ) {
361389 const url = `${ brokerBaseUrl } ${ pathname } ` ;
362390 const response = await fetch ( url , {
@@ -389,26 +417,30 @@ async function brokerFetch(pathname, body) {
389417
390418async function pullInbox ( ) {
391419 const timestamp = Math . floor ( Date . now ( ) / 1000 ) ;
392- const signature = signRequest ( "inbox.pull" , timestamp , String ( MAX_MESSAGES ) ) ;
420+ const signature = signPullRequest ( timestamp , MAX_MESSAGES , BROKER_WAIT_SECONDS ) ;
393421
394- const payload = await brokerFetch ( "/api/inbox/pull" , {
422+ const body = {
395423 workspace_id : workspaceId ,
424+ protocol_version : INBOX_PROTOCOL_VERSION ,
396425 max_messages : MAX_MESSAGES ,
426+ wait_seconds : BROKER_WAIT_SECONDS ,
397427 timestamp,
398428 signature,
399- } ) ;
429+ } ;
430+
431+ const payload = await brokerFetch ( "/api/inbox/pull" , body ) ;
400432
401433 return Array . isArray ( payload . messages ) ? payload . messages : [ ] ;
402434}
403435
404436async function ackInbox ( messageIds ) {
405437 if ( messageIds . length === 0 ) return ;
406438 const timestamp = Math . floor ( Date . now ( ) / 1000 ) ;
407- const joined = messageIds . join ( "," ) ;
408- const signature = signRequest ( "inbox.ack" , timestamp , joined ) ;
439+ const signature = signProtocolRequest ( "inbox.ack" , timestamp , { message_ids : messageIds } ) ;
409440
410441 await brokerFetch ( "/api/inbox/ack" , {
411442 workspace_id : workspaceId ,
443+ protocol_version : INBOX_PROTOCOL_VERSION ,
412444 message_ids : messageIds ,
413445 timestamp,
414446 signature,
@@ -918,7 +950,9 @@ async function startPollLoop() {
918950 }
919951
920952 backoffMs = POLL_INTERVAL_MS ;
921- await sleep ( POLL_INTERVAL_MS ) ;
953+ if ( BROKER_WAIT_SECONDS <= 0 ) {
954+ await sleep ( POLL_INTERVAL_MS ) ;
955+ }
922956 } catch ( err ) {
923957 if ( ! pollSucceeded ) {
924958 markHealth ( "poll" , false , err ) ;
@@ -960,7 +994,11 @@ async function startPollLoop() {
960994 logInfo ( ` outbound mode: ${ outboundMode } ${ outboundMode === "direct" ? "(using SLACK_BOT_TOKEN)" : "(via broker)" } ` ) ;
961995 logInfo ( ` broker: ${ brokerBaseUrl } ` ) ;
962996 logInfo ( ` workspace: ${ workspaceId } ` ) ;
963- logInfo ( ` poll interval: ${ POLL_INTERVAL_MS } ms, max messages: ${ MAX_MESSAGES } ` ) ;
997+ logInfo ( ` inbox protocol: ${ INBOX_PROTOCOL_VERSION } ` ) ;
998+ logInfo (
999+ ` poll mode: ${ BROKER_WAIT_SECONDS > 0 ? `long-poll (${ BROKER_WAIT_SECONDS } s)` : "short-poll" } , ` +
1000+ `interval: ${ POLL_INTERVAL_MS } ms, max messages: ${ MAX_MESSAGES } ` ,
1001+ ) ;
9641002 logInfo ( ` allowed users: ${ ALLOWED_USERS . length || "all" } ` ) ;
9651003 logInfo ( ` pi socket: ${ socketPath || "(not found — will retry on message)" } ` ) ;
9661004 await startPollLoop ( ) ;
0 commit comments