openclaw-seatalk 0.3.3 → 0.4.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0
4
+
5
+ - Route DM and group threads to isolated agent sessions; new threads fork the parent transcript on first reply.
6
+ - Outbound messages from agent-initiated sends now stay in the originating thread.
7
+ - Switch to structured logging across all inbound and outbound paths.
8
+
3
9
  ## 0.3.3
4
10
 
5
11
  - Publish bundled JS only (`dist/*.js`), fixing install failure on OpenClaw versions that require compiled runtime output for TypeScript entries.
package/README.md CHANGED
@@ -8,7 +8,7 @@ OpenClaw channel plugin for [SeaTalk](https://seatalk.io/) messaging.
8
8
 
9
9
  - **Private chat** — bidirectional text, image, file messaging with bot subscribers
10
10
  - **Group chat** — receive @mentioned messages, send text/image/file replies; configurable group allow-list and per-sender access control
11
- - **Thread messages** — full support for DM threads and group threads; replies are routed back to the originating thread
11
+ - **Thread messages** — each DM or group thread runs as an isolated agent session that inherits the parent transcript on first reply; replies are routed back to the originating thread
12
12
  - **Quoted messages** — inbound messages with `quoted_message_id` are automatically resolved (text + media download) and provided to the AI as context
13
13
  - **Media handling** — inbound: image/file/video URL download; outbound: image/file base64 upload; video receive-only
14
14
  - **Typing indicator** — one-shot typing status via SeaTalk API for both private and group chats (configurable: `typing` or `off`)
@@ -199,9 +199,12 @@ Or edit the OpenClaw config file directly (`~/.openclaw/openclaw.json`).
199
199
  | `relayUrl` | string | — | WebSocket URL (relay mode only) |
200
200
  | `dmPolicy` | `"open"` \| `"allowlist"` \| `"pairing"` | `"allowlist"` | Who can DM the bot (`pairing`: approve via CLI) |
201
201
  | `allowFrom` | string[] | — | Allowed DM senders (employee codes or emails) |
202
+ | `dmThreadSession` | boolean | `true` | Route each DM thread to its own agent session |
202
203
  | `groupPolicy` | `"disabled"` \| `"allowlist"` \| `"open"` | `"disabled"` | Group chat policy |
203
204
  | `groupAllowFrom` | string[] | — | Allowed group IDs (when `groupPolicy: "allowlist"`) |
204
205
  | `groupSenderAllowFrom` | string[] | — | Allowed senders within groups (employee codes or emails) |
206
+ | `groupThreadSession` | boolean | `true` | Route each group thread to its own agent session |
207
+ | `threadInheritParent` | boolean | `true` | Fork the parent transcript into new thread sessions on first reply |
205
208
  | `outboundCoalescing` | boolean | `true` | Merge consecutive reply payloads into a single message (4000-char chunking) |
206
209
  | `processingIndicator` | `"typing"` \| `"off"` | `"typing"` | Show typing status while processing |
207
210
  | `mediaAllowHosts` | string[] | `["openapi.seatalk.io"]` | Allowed hostnames for inbound media downloads (HTTPS only) |
package/dist/index.js CHANGED
@@ -1,20 +1,20 @@
1
- import {buildChannelConfigSchema,defineChannelPluginEntry,DEFAULT_ACCOUNT_ID,normalizeAccountId,PAIRING_APPROVED_MESSAGE}from'openclaw/plugin-sdk/core';import*as j from'fs';import*as tt from'os';import*as J from'path';import {resolveSendableOutboundReplyParts,sendMediaWithLeadingCaption}from'openclaw/plugin-sdk/reply-payload';import {createPairingPrefixStripper,createChannelPairingController}from'openclaw/plugin-sdk/channel-pairing';import {resolveDmGroupAccessWithLists,DM_GROUP_ACCESS_REASON}from'openclaw/plugin-sdk/channel-policy';import En from'ws';import*as $e from'crypto';import*as Rt from'http';import {z}from'zod';import {createTopLevelChannelDmPolicy,createStandardChannelSetupStatus,mergeAllowFromEntries}from'openclaw/plugin-sdk/setup';import {Type}from'@sinclair/typebox';var qt=Object.defineProperty;var U=(e,t)=>()=>(e&&(t=e(e=0)),t);var ze=(e,t)=>{for(var a in t)qt(e,a,{get:t[a],enumerable:true});};function zt(e){let t=e.channels?.seatalk?.accounts;return !t||typeof t!="object"?[]:Object.keys(t).filter(Boolean)}function ye(e){let t=zt(e);return t.length===0?[DEFAULT_ACCOUNT_ID]:[...t].toSorted((a,n)=>a.localeCompare(n))}function Pe(e){let t=ye(e);return t.includes(DEFAULT_ACCOUNT_ID)?DEFAULT_ACCOUNT_ID:t[0]??DEFAULT_ACCOUNT_ID}function Wt(e,t){let a=e.channels?.seatalk?.accounts;if(!(!a||typeof a!="object"))return a[t]}function Kt(e,t){let a=e.channels?.seatalk,{accounts:n,...o}=a??{},r=Wt(e,t)??{};return {...o,...r}}function q(e){let t=e?.appId?.trim(),a=e?.appSecret?.trim(),n=e?.signingSecret?.trim();return !t||!a||!n?null:{appId:t,appSecret:a,signingSecret:n}}function A(e){let t=normalizeAccountId(e.accountId),n=e.cfg.channels?.seatalk?.enabled!==false,o=Kt(e.cfg,t),r=o.enabled!==false,s=n&&r,l=q(o);return {accountId:t,enabled:s,configured:!!l,appId:l?.appId,appSecret:l?.appSecret,mode:o.mode??"webhook",relayUrl:o.relayUrl,webhookPort:o.webhookPort??8080,webhookPath:o.webhookPath??"/callback",tools:o.tools,config:o}}function ne(e){return ye(e).map(t=>A({cfg:e,accountId:t})).filter(t=>t.enabled&&t.configured)}var N=U(()=>{});function xe(e,t){let a=`${e}:${t}`,n=Ve.get(a);return n||(n=new Me(e,t),Ve.set(a,n)),n}function F(e){return !e.appId||!e.appSecret?null:xe(e.appId,e.appSecret)}var We,Ke,Me,Ve,K=U(()=>{We="https://openapi.seatalk.io",Ke=[1e4,6e4],Me=class{appId;appSecret;tokenInfo=null;tokenPromise=null;constructor(t,a){this.appId=t,this.appSecret=a;}async getAccessToken(){if(this.tokenInfo){let t=Math.floor(Date.now()/1e3);if(this.tokenInfo.expireAt-t>600)return this.tokenInfo.token}return (await this.refreshToken()).token}async refreshToken(){if(this.tokenPromise)return this.tokenPromise;this.tokenPromise=this._fetchToken();try{let t=await this.tokenPromise;return this.tokenInfo=t,t}finally{this.tokenPromise=null;}}async _fetchToken(){let t=new AbortController,a=setTimeout(()=>t.abort(),1e4);try{let n=await fetch(`${We}/auth/app_access_token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({app_id:this.appId,app_secret:this.appSecret}),signal:t.signal});if(!n.ok){let r=n.headers.get("x-rid")??void 0;throw new Error(`SeaTalk token request failed: HTTP ${n.status} (x-rid: ${r})`)}let o=await n.json();if(o.code!==0)throw new Error(`SeaTalk token error: code=${o.code} message=${o.message??"unknown"}`);if(!o.app_access_token||!o.expire)throw new Error("SeaTalk token response missing token or expire");return {token:o.app_access_token,expireAt:o.expire}}finally{clearTimeout(a);}}async apiCall(t,a,n,o=true,r=0){let s=await this.getAccessToken(),l=new AbortController,c=setTimeout(()=>l.abort(),1e4),i;try{let g=await fetch(`${We}${a}`,{method:t,headers:{Authorization:`Bearer ${s}`,"Content-Type":"application/json"},body:n?JSON.stringify(n):void 0,signal:l.signal});if(i=g.headers.get("x-rid")??void 0,!g.ok)throw Object.assign(new Error(`SeaTalk API error: HTTP ${g.status} (x-rid: ${i})`),{httpStatus:g.status,xRid:i});let d=await g.json();if(d.code===100&&o)return await this.refreshToken(),this.apiCall(t,a,n,!1);if(d.code===101){if(r<Ke.length){let p=Ke[r];return await new Promise(f=>setTimeout(f,p)),this.apiCall(t,a,n,o,r+1)}throw Object.assign(new Error(`SeaTalk rate limit exceeded after ${r+1} attempts (x-rid: ${i})`),{code:101,xRid:i})}if(d.code!==0)throw Object.assign(new Error(`SeaTalk API error: code=${d.code} message=${d.message??"unknown"} (x-rid: ${i})`),{code:d.code,xRid:i});return d}catch(g){throw i&&g instanceof Error&&!g.message.includes("x-rid:")&&(g.message+=` (x-rid: ${i})`),g}finally{clearTimeout(c);}}async sendSingleChat(t,a,n){let o=n?{...a,thread_id:n}:a;await this.apiCall("POST","/messaging/v2/single_chat",{employee_code:t,message:o});}async sendGroupChat(t,a,n){let o=n?{...a,thread_id:n}:a;await this.apiCall("POST","/messaging/v2/group_chat",{group_id:t,message:o});}async setSingleChatTyping(t,a){let n={employee_code:t};a&&(n.thread_id=a),await this.apiCall("POST","/messaging/v2/single_chat_typing",n);}async setGroupChatTyping(t,a){let n={group_id:t};a&&(n.thread_id=a),await this.apiCall("POST","/messaging/v2/group_chat_typing",n);}async downloadMedia(t){let a=await this.getAccessToken(),n=new AbortController,o=setTimeout(()=>n.abort(),6e4);try{let r=await fetch(t,{headers:{Authorization:`Bearer ${a}`},signal:n.signal});if(!r.ok)throw new Error(`SeaTalk media download failed: HTTP ${r.status}`);let s=r.headers.get("content-type")??"application/octet-stream",l=await r.arrayBuffer();return {buffer:Buffer.from(l),contentType:s}}finally{clearTimeout(o);}}async getEmployeeCodeByEmail(t){let n=[];for(let o=0;o<t.length;o+=500){let r=t.slice(o,o+500),s=await this.apiCall("POST","/contacts/v2/get_employee_code_with_email",{emails:r});for(let l of s.employees??[])n.push({email:l.email,employeeCode:l.employee_code,status:l.employee_status});}return n}async getGroupChatHistory(t,a){let n=new URLSearchParams({group_id:t,page_size:String(a?.pageSize??50)});return a?.cursor&&n.set("cursor",a.cursor),this.apiCall("GET",`/messaging/v2/group_chat/history?${n}`)}async getJoinedGroupChats(t){let a=new URLSearchParams;t?.pageSize&&a.set("page_size",String(t.pageSize)),t?.cursor&&a.set("cursor",t.cursor);let n=a.toString();return this.apiCall("GET",`/messaging/v2/group_chat/joined${n?`?${n}`:""}`)}async getGroupChatInfo(t){return this.apiCall("GET",`/messaging/v2/group_chat/info?group_id=${encodeURIComponent(t)}`)}async getDmThread(t,a,n){let o=new URLSearchParams({employee_code:t,thread_id:a});return n?.pageSize&&o.set("page_size",String(n.pageSize)),n?.cursor&&o.set("cursor",n.cursor),this.apiCall("GET",`/messaging/v2/single_chat/get_thread_by_thread_id?${o}`)}async getGroupThread(t,a,n){let o=new URLSearchParams({group_id:t,thread_id:a});return n?.pageSize&&o.set("page_size",String(n.pageSize)),n?.cursor&&o.set("cursor",n.cursor),this.apiCall("GET",`/messaging/v2/group_chat/get_thread_by_thread_id?${o}`)}async getMessageByMessageId(t){return this.apiCall("GET",`/messaging/v2/get_message_by_message_id?message_id=${encodeURIComponent(t)}`)}getAppId(){return this.appId}},Ve=new Map;});function et(e){Re=e;}function V(){if(!Re)throw new Error("SeaTalk runtime not initialized");return Re}var Re,ce=U(()=>{Re=null;});function tn(e){let t=e&&e.length>0?e:[...en];return new Set(t.map(a=>a.trim().toLowerCase()).filter(Boolean))}function nn(e,t){let a;try{a=new URL(e);}catch{return {ok:false,detail:"invalid URL"}}if(a.protocol!=="https:")return {ok:false,detail:`only https allowed (got ${a.protocol})`};let n=a.hostname.toLowerCase();return t.has(n)?{ok:true,hostname:n}:{ok:false,detail:`host not in allowlist (${n})`}}async function de(e){let{message:t,client:a,log:n,mediaAllowHosts:o}=e,r=V(),s=tn(o),l,c,i=Oe.file;switch(t.tag){case "image":l=t.image?.content,i=Oe.image;break;case "file":l=t.file?.content,c=t.file?.filename;break;case "video":l=t.video?.content,i=Oe.video;break;default:return null}if(!l)return null;let g=nn(l,s);if(!g.ok)return n?.(`seatalk: rejected inbound ${t.tag} media before download: ${g.detail}`),null;n?.(`seatalk: inbound ${t.tag} media url host=${g.hostname}`);let d=1;for(let p=0;p<=d;p++)try{let f=await a.downloadMedia(l),u=f.contentType;if((!u||u==="application/octet-stream")&&f.buffer.length<Qt){let h=await r.media.detectMime({buffer:f.buffer});h&&(u=h);}let S=await r.channel.media.saveMediaBuffer(f.buffer,u??"application/octet-stream","inbound",Zt,c);return n?.(`seatalk: downloaded ${t.tag} media, saved to ${S.path}`),{path:S.path,contentType:S.contentType,filename:c,placeholder:i}}catch(f){if(p<d){n?.(`seatalk: retry ${p+1}/${d} downloading ${t.tag} media: ${String(f)}`);continue}return n?.(`seatalk: failed to download ${t.tag} media after ${d+1} attempts: ${String(f)}`),null}return null}async function an(e){let t=new AbortController,a=setTimeout(()=>t.abort(),3e4);try{let n=await fetch(e,{signal:t.signal});if(!n.ok)throw new Error(`Failed to fetch media from ${e}: HTTP ${n.status}`);let o=await n.arrayBuffer(),r=Buffer.from(o),s=new URL(e).pathname;return {buffer:r,name:J.basename(s)||"file"}}finally{clearTimeout(a);}}function on(e){let t=e.startsWith("~")?J.join(tt.homedir(),e.slice(1)):e.replace(/^file:\/\//,"");if(!j.existsSync(t))throw new Error(`Media file not found: ${t}`);let a=j.openSync(t,"r");try{let{size:n}=j.fstatSync(a),o=Buffer.alloc(n),r=0;for(;r<n;){let l=j.readSync(a,o,r,n-r,r);if(l===0)break;r+=l;}return {buffer:r<n?o.subarray(0,r):o,name:J.basename(t)}}finally{j.closeSync(a);}}async function nt(e){let t=e.startsWith("http://")||e.startsWith("https://"),{buffer:a,name:n}=t?await an(e):on(e);if(a.length>Yt)throw new Error(`Media file too large: ${(a.length/1024/1024).toFixed(1)}MB exceeds ~3.75MB limit`);let o=J.extname(n).toLowerCase(),r=Xt.has(o)?"image":"file";return {base64:a.toString("base64"),sendAs:r,filename:r==="file"?n.slice(0,100):void 0}}function De(e){let t=e[0],a=e.map(o=>o.path),n=e.map(o=>o.contentType).filter(Boolean);return {MediaPath:t?.path,MediaType:t?.contentType,MediaUrl:t?.path,MediaPaths:a.length>0?a:void 0,MediaUrls:a.length>0?a:void 0,MediaTypes:n.length>0?n:void 0}}var Oe,Xt,Yt,Qt,Zt,en,Se=U(()=>{ce();Oe={image:"<media:image>",file:"<media:document>",video:"<media:video>"},Xt=new Set([".png",".jpg",".jpeg",".gif"]),Yt=3.75*1024*1024,Qt=10*1024*1024,Zt=250*1024*1024,en=["openapi.seatalk.io"];});async function H(e,t,a,n=1,o){await e.sendSingleChat(t,{tag:"text",text:{format:n,content:a}},o);}async function at(e,t,a,n){await e.sendSingleChat(t,{tag:"image",image:{content:a}},n);}async function ot(e,t,a,n,o){await e.sendSingleChat(t,{tag:"file",file:{content:a,filename:n}},o);}async function ae(e,t,a,n=1,o){await e.sendGroupChat(t,{tag:"text",text:{format:n,content:a}},o);}async function we(e){let{client:t,to:a,mediaUrl:n,threadId:o,isGroup:r}=e,s=await nt(n);s&&(r?s.sendAs==="image"?await t.sendGroupChat(a,{tag:"image",image:{content:s.base64}},o):await t.sendGroupChat(a,{tag:"file",file:{content:s.base64,filename:s.filename||"file"}},o):s.sendAs==="image"?await at(t,a,s.base64,o):await ot(t,a,s.base64,s.filename||"file",o));}var oe=U(()=>{Se();});function ht(e){let{groupPolicy:t,groupAllowFrom:a,groupSenderAllowFrom:n,groupId:o,senderEmployeeCode:r,senderEmail:s}=e;return t==="disabled"?{allowed:false,reason:"groupPolicy is disabled"}:t==="allowlist"&&!(a??[]).includes(o)?{allowed:false,reason:`group ${o} not in groupAllowFrom`}:n&&n.length>0&&!n.some(c=>{let i=c.trim();return !!(i==="*"||i===r||s&&i.toLowerCase()===s.toLowerCase())})?{allowed:false,reason:`sender ${r} not in groupSenderAllowFrom`}:{allowed:true}}var kt=U(()=>{});function gn(e){let t=e.sender,a=e.message_sent_time,n=[];return t?.email?n.push(t.email):t?.employee_code&&n.push(t.employee_code),a&&n.push(new Date(a*1e3).toISOString()),n.length>0?`[${n.join(" ")}] `:""}function pn(e){let t=e.tag;return {message_id:e.message_id??"",tag:t,text:e.text,image:e.image,file:e.file,video:e.video,combined_forwarded_chat_history:e.combined_forwarded_chat_history}}async function yt(e,t){let a=e.tag,n=[];if(a==="text"){let o=e.text;return {text:o?.plain_text??o?.content??"",media:n}}if(a==="image"||a==="file"||a==="video"){let o=await de({message:pn(e),client:t.client,mediaAllowHosts:t.mediaAllowHosts,log:t.log});return o?(n.push(o),{text:o.placeholder,media:n}):{text:`<media:${a}>`,media:n}}if(a==="combined_forwarded_chat_history"){let o=e.combined_forwarded_chat_history?.content;if(o){let r=await ge(o,t);return n.push(...r.media),{text:r.lines.length>0?`[Forwarded messages]
1
+ import {buildChannelConfigSchema,defineChannelPluginEntry,DEFAULT_ACCOUNT_ID,normalizeAccountId,stripChannelTargetPrefix,buildChannelOutboundSessionRoute,buildThreadAwareOutboundSessionRoute,PAIRING_APPROVED_MESSAGE}from'openclaw/plugin-sdk/core';import {getChildLogger}from'openclaw/plugin-sdk/runtime-env';import*as j from'fs';import*as st from'os';import*as Z from'path';import {resolveSendableOutboundReplyParts,sendMediaWithLeadingCaption}from'openclaw/plugin-sdk/reply-payload';import {createPairingPrefixStripper,createChannelPairingController}from'openclaw/plugin-sdk/channel-pairing';import {resolveDmGroupAccessWithLists,DM_GROUP_ACCESS_REASON}from'openclaw/plugin-sdk/channel-policy';import {resolveThreadSessionKeys}from'openclaw/plugin-sdk/routing';import zn from'ws';import*as Ae from'crypto';import*as qt from'http';import {z as z$1}from'zod';import {createTopLevelChannelDmPolicy,createStandardChannelSetupStatus,mergeAllowFromEntries}from'openclaw/plugin-sdk/setup';import {normalizeOptionalString}from'openclaw/plugin-sdk/text-runtime';import {Type}from'@sinclair/typebox';var Zt=Object.defineProperty;var B=(e,t)=>()=>(e&&(t=e(e=0)),t);var Ve=(e,t)=>{for(var o in t)Zt(e,o,{get:t[o],enumerable:true});};function tn(e){let t=e.channels?.seatalk?.accounts;return !t||typeof t!="object"?[]:Object.keys(t).filter(Boolean)}function be(e){let t=tn(e);return t.length===0?[DEFAULT_ACCOUNT_ID]:[...t].toSorted((o,n)=>o.localeCompare(n))}function Oe(e){let t=be(e);return t.includes(DEFAULT_ACCOUNT_ID)?DEFAULT_ACCOUNT_ID:t[0]??DEFAULT_ACCOUNT_ID}function nn(e,t){let o=e.channels?.seatalk?.accounts;if(!(!o||typeof o!="object"))return o[t]}function on(e,t){let o=e.channels?.seatalk,{accounts:n,...a}=o??{},r=nn(e,t)??{};return {...a,...r}}function z(e){let t=e?.appId?.trim(),o=e?.appSecret?.trim(),n=e?.signingSecret?.trim();return !t||!o||!n?null:{appId:t,appSecret:o,signingSecret:n}}function x(e){let t=normalizeAccountId(e.accountId),n=e.cfg.channels?.seatalk?.enabled!==false,a=on(e.cfg,t),r=a.enabled!==false,i=n&&r,s=z(a);return {accountId:t,enabled:i,configured:!!s,appId:s?.appId,appSecret:s?.appSecret,mode:a.mode??"webhook",relayUrl:a.relayUrl,webhookPort:a.webhookPort??8080,webhookPath:a.webhookPath??"/callback",tools:a.tools,config:a}}function ie(e){return be(e).map(t=>x({cfg:e,accountId:t})).filter(t=>t.enabled&&t.configured)}var N=B(()=>{});function v(e){let t=Je.get(e);if(t)return t;let o=getChildLogger({channel:"seatalk",module:e}),n={debug:(a,r)=>r?o.debug?.(a,r):o.debug?.(a),info:(a,r)=>r?o.info(a,r):o.info(a),warn:(a,r)=>r?o.warn(a,r):o.warn(a),error:(a,r)=>r?o.error(a,r):o.error(a)};return Je.set(e,n),n}var Je,X=B(()=>{Je=new Map;});function De(e,t){let o=`${e}:${t}`,n=Ze.get(o);return n||(n=new $e(e,t),Ze.set(o,n)),n}function F(e){return !e.appId||!e.appSecret?null:De(e.appId,e.appSecret)}var Xe,Ye,rn,Qe,$e,Ze,Y=B(()=>{X();Xe="https://openapi.seatalk.io",Ye=1e4,rn=600,Qe=[1e4,6e4],$e=class{appId;appSecret;tokenInfo=null;tokenPromise=null;constructor(t,o){this.appId=t,this.appSecret=o;}async getAccessToken(){if(this.tokenInfo){let t=Math.floor(Date.now()/1e3);if(this.tokenInfo.expireAt-t>rn)return this.tokenInfo.token}return (await this.refreshToken()).token}async refreshToken(){if(this.tokenPromise)return this.tokenPromise;this.tokenPromise=this._fetchToken();try{let t=await this.tokenPromise;return this.tokenInfo=t,t}finally{this.tokenPromise=null;}}async _fetchToken(){let t=new AbortController,o=setTimeout(()=>t.abort(),Ye);try{let n=await fetch(`${Xe}/auth/app_access_token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({app_id:this.appId,app_secret:this.appSecret}),signal:t.signal});if(!n.ok){let r=n.headers.get("x-rid")??void 0;throw new Error(`SeaTalk token request failed: HTTP ${n.status} (x-rid: ${r})`)}let a=await n.json();if(a.code!==0)throw new Error(`SeaTalk token error: code=${a.code} message=${a.message??"unknown"}`);if(!a.app_access_token||!a.expire)throw new Error("SeaTalk token response missing token or expire");return {token:a.app_access_token,expireAt:a.expire}}finally{clearTimeout(o);}}async apiCall(t,o,n,a=true,r=0){let i=await this.getAccessToken(),s=new AbortController,l=setTimeout(()=>s.abort(),Ye),d;try{let u=await fetch(`${Xe}${o}`,{method:t,headers:{Authorization:`Bearer ${i}`,"Content-Type":"application/json"},body:n?JSON.stringify(n):void 0,signal:s.signal});if(d=u.headers.get("x-rid")??void 0,!u.ok)throw Object.assign(new Error(`SeaTalk API error: HTTP ${u.status} (x-rid: ${d})`),{httpStatus:u.status,xRid:d});let c=await u.json();if(c.code===100&&a)return await this.refreshToken(),this.apiCall(t,o,n,!1);if(c.code===101){if(r<Qe.length){let f=Qe[r];return v("client").warn("rate limited, retrying",{path:o,attempt:r+1,delayMs:f,xRid:d}),await new Promise(g=>setTimeout(g,f)),this.apiCall(t,o,n,a,r+1)}throw Object.assign(new Error(`SeaTalk rate limit exceeded after ${r+1} attempts (x-rid: ${d})`),{code:101,xRid:d})}if(c.code!==0)throw Object.assign(new Error(`SeaTalk API error: code=${c.code} message=${c.message??"unknown"} (x-rid: ${d})`),{code:c.code,xRid:d});return c}catch(u){throw d&&u instanceof Error&&!u.message.includes("x-rid:")&&(u.message+=` (x-rid: ${d})`),u}finally{clearTimeout(l);}}async sendSingleChat(t,o,n){let a=o.tag;v("outbound").info("dm send",{employeeCode:t,tag:a,threadId:n});let r=n?{...o,thread_id:n}:o;await this.apiCall("POST","/messaging/v2/single_chat",{employee_code:t,message:r});}async sendGroupChat(t,o,n){let a=o.tag;v("outbound").info("group send",{groupId:t,tag:a,threadId:n});let r=n?{...o,thread_id:n}:o;await this.apiCall("POST","/messaging/v2/group_chat",{group_id:t,message:r});}async setSingleChatTyping(t,o){let n={employee_code:t};o&&(n.thread_id=o),await this.apiCall("POST","/messaging/v2/single_chat_typing",n);}async setGroupChatTyping(t,o){let n={group_id:t};o&&(n.thread_id=o),await this.apiCall("POST","/messaging/v2/group_chat_typing",n);}async downloadMedia(t){let o=await this.getAccessToken(),n=new AbortController,a=setTimeout(()=>n.abort(),6e4);try{let r=await fetch(t,{headers:{Authorization:`Bearer ${o}`},signal:n.signal});if(!r.ok)throw new Error(`SeaTalk media download failed: HTTP ${r.status}`);let i=r.headers.get("content-type")??"application/octet-stream",s=await r.arrayBuffer();return {buffer:Buffer.from(s),contentType:i}}finally{clearTimeout(a);}}async getEmployeeCodeByEmail(t){let n=[];for(let a=0;a<t.length;a+=500){let r=t.slice(a,a+500),i=await this.apiCall("POST","/contacts/v2/get_employee_code_with_email",{emails:r});for(let s of i.employees??[])n.push({email:s.email,employeeCode:s.employee_code,status:s.employee_status});}return n}async getGroupChatHistory(t,o){let n=new URLSearchParams({group_id:t,page_size:String(o?.pageSize??50)});return o?.cursor&&n.set("cursor",o.cursor),this.apiCall("GET",`/messaging/v2/group_chat/history?${n}`)}async getJoinedGroupChats(t){let o=new URLSearchParams;t?.pageSize&&o.set("page_size",String(t.pageSize)),t?.cursor&&o.set("cursor",t.cursor);let n=o.toString();return this.apiCall("GET",`/messaging/v2/group_chat/joined${n?`?${n}`:""}`)}async getGroupChatInfo(t){return this.apiCall("GET",`/messaging/v2/group_chat/info?group_id=${encodeURIComponent(t)}`)}async getDmThread(t,o,n){let a=new URLSearchParams({employee_code:t,thread_id:o});return n?.pageSize&&a.set("page_size",String(n.pageSize)),n?.cursor&&a.set("cursor",n.cursor),this.apiCall("GET",`/messaging/v2/single_chat/get_thread_by_thread_id?${a}`)}async getGroupThread(t,o,n){let a=new URLSearchParams({group_id:t,thread_id:o});return n?.pageSize&&a.set("page_size",String(n.pageSize)),n?.cursor&&a.set("cursor",n.cursor),this.apiCall("GET",`/messaging/v2/group_chat/get_thread_by_thread_id?${a}`)}async getMessageByMessageId(t){return this.apiCall("GET",`/messaging/v2/get_message_by_message_id?message_id=${encodeURIComponent(t)}`)}getAppId(){return this.appId}},Ze=new Map;});function rt(e){Be=e;}function Q(){if(!Be)throw new Error("SeaTalk runtime not initialized");return Be}var Be,fe=B(()=>{Be=null;});function fn(e){let t=e&&e.length>0?e:[...pn];return new Set(t.map(o=>o.trim().toLowerCase()).filter(Boolean))}function mn(e,t){let o;try{o=new URL(e);}catch{return {ok:false,detail:"invalid URL"}}if(o.protocol!=="https:")return {ok:false,detail:`only https allowed (got ${o.protocol})`};let n=o.hostname.toLowerCase();return t.has(n)?{ok:true,hostname:n}:{ok:false,detail:`host not in allowlist (${n})`}}async function me(e){let{message:t,client:o,mediaAllowHosts:n}=e,a=v("media"),r=Q(),i=fn(n),s,l,d=Fe.file;switch(t.tag){case "image":s=t.image?.content,d=Fe.image;break;case "file":s=t.file?.content,l=t.file?.filename;break;case "video":s=t.video?.content,d=Fe.video;break;default:return null}if(!s)return null;let u=mn(s,i);if(!u.ok)return a.warn("media rejected",{tag:t.tag,detail:u.detail}),null;a.info("media url",{tag:t.tag,host:u.hostname});let c=1;for(let f=0;f<=c;f++)try{let g=await o.downloadMedia(s),m=g.contentType;if((!m||m==="application/octet-stream")&&g.buffer.length<un){let h=await r.media.detectMime({buffer:g.buffer});h&&(m=h);}let y=await r.channel.media.saveMediaBuffer(g.buffer,m??"application/octet-stream","inbound",gn,l);return a.info("media downloaded",{tag:t.tag,path:y.path}),{path:y.path,contentType:y.contentType,filename:l,placeholder:d}}catch(g){if(f<c){a.warn("media download retry",{tag:t.tag,attempt:f+1,maxRetry:c,err:String(g)});continue}return a.error("media download failed",{tag:t.tag,attempts:c+1,err:String(g)}),null}return null}async function hn(e){let t=new AbortController,o=setTimeout(()=>t.abort(),3e4);try{let n=await fetch(e,{signal:t.signal});if(!n.ok)throw new Error(`Failed to fetch media from ${e}: HTTP ${n.status}`);let a=await n.arrayBuffer(),r=Buffer.from(a),i=new URL(e).pathname;return {buffer:r,name:Z.basename(i)||"file"}}finally{clearTimeout(o);}}function yn(e){let t=e.startsWith("~")?Z.join(st.homedir(),e.slice(1)):e.replace(/^file:\/\//,"");if(!j.existsSync(t))throw new Error(`Media file not found: ${t}`);let o=j.openSync(t,"r");try{let{size:n}=j.fstatSync(o),a=Buffer.alloc(n),r=0;for(;r<n;){let s=j.readSync(o,a,r,n-r,r);if(s===0)break;r+=s;}return {buffer:r<n?a.subarray(0,r):a,name:Z.basename(t)}}finally{j.closeSync(o);}}async function it(e){let t=e.startsWith("http://")||e.startsWith("https://"),{buffer:o,name:n}=t?await hn(e):yn(e);if(o.length>dn)throw new Error(`Media file too large: ${(o.length/1024/1024).toFixed(1)}MB exceeds ~3.75MB limit`);let a=Z.extname(n).toLowerCase(),r=cn.has(a)?"image":"file";return {base64:o.toString("base64"),sendAs:r,filename:r==="file"?n.slice(0,100):void 0}}function Le(e){let t=e[0],o=e.map(a=>a.path),n=e.map(a=>a.contentType).filter(Boolean);return {MediaPath:t?.path,MediaType:t?.contentType,MediaUrl:t?.path,MediaPaths:o.length>0?o:void 0,MediaUrls:o.length>0?o:void 0,MediaTypes:n.length>0?n:void 0}}var Fe,cn,dn,un,gn,pn,Ce=B(()=>{X();fe();Fe={image:"<media:image>",file:"<media:document>",video:"<media:video>"},cn=new Set([".png",".jpg",".jpeg",".gif"]),dn=3.75*1024*1024,un=10*1024*1024,gn=250*1024*1024,pn=["openapi.seatalk.io"];});async function H(e,t,o,n=1,a){await e.sendSingleChat(t,{tag:"text",text:{format:n,content:o}},a);}async function lt(e,t,o,n){await e.sendSingleChat(t,{tag:"image",image:{content:o}},n);}async function ct(e,t,o,n,a){await e.sendSingleChat(t,{tag:"file",file:{content:o,filename:n}},a);}async function le(e,t,o,n=1,a){await e.sendGroupChat(t,{tag:"text",text:{format:n,content:o}},a);}async function ve(e){let{client:t,to:o,mediaUrl:n,threadId:a,isGroup:r}=e,i=await it(n);i&&(r?i.sendAs==="image"?await t.sendGroupChat(o,{tag:"image",image:{content:i.base64}},a):await t.sendGroupChat(o,{tag:"file",file:{content:i.base64,filename:i.filename||"file"}},a):i.sendAs==="image"?await lt(t,o,i.base64,a):await ct(t,o,i.base64,i.filename||"file",a));}var ce=B(()=>{Ce();});function _t(e){let{groupPolicy:t,groupAllowFrom:o,groupSenderAllowFrom:n,groupId:a,senderEmployeeCode:r,senderEmail:i}=e;return t==="disabled"?{allowed:false,reason:"groupPolicy is disabled"}:t==="allowlist"&&!(o??[]).includes(a)?{allowed:false,reason:`group ${a} not in groupAllowFrom`}:n&&n.length>0&&!n.some(l=>{let d=l.trim();return !!(d==="*"||d===r||i&&d.toLowerCase()===i.toLowerCase())})?{allowed:false,reason:`sender ${r} not in groupSenderAllowFrom`}:{allowed:true}}var It=B(()=>{});function Pn(e){let t=e.sender,o=e.message_sent_time,n=[];return t?.email?n.push(t.email):t?.employee_code&&n.push(t.employee_code),o&&n.push(new Date(o*1e3).toISOString()),n.length>0?`[${n.join(" ")}] `:""}function xn(e){let t=e.tag;return {message_id:e.message_id??"",tag:t,text:e.text,image:e.image,file:e.file,video:e.video,combined_forwarded_chat_history:e.combined_forwarded_chat_history}}async function Pt(e,t){let o=e.tag,n=[];if(o==="text"){let a=e.text;return {text:a?.plain_text??a?.content??"",media:n}}if(o==="image"||o==="file"||o==="video"){let a=await me({message:xn(e),client:t.client,mediaAllowHosts:t.mediaAllowHosts});return a?(n.push(a),{text:a.placeholder,media:n}):{text:`<media:${o}>`,media:n}}if(o==="combined_forwarded_chat_history"){let a=e.combined_forwarded_chat_history?.content;if(a){let r=await ye(a,t);return n.push(...r.media),{text:r.lines.length>0?`[Forwarded messages]
2
2
  ${r.lines.join(`
3
- `)}`:"[Forwarded messages]",media:n}}return {text:"[Forwarded messages]",media:n}}return {text:`<unsupported:${a??"unknown"}>`,media:n}}async function ge(e,t){let a=[],n=[];for(let o of e){if(Array.isArray(o)){let c=await ge(o,t);a.push(...c.lines),n.push(...c.media);continue}if(!o||typeof o!="object")continue;let r=o,s=gn(r),l=await yt(r,t);n.push(...l.media),l.text&&a.push(`${s}${l.text}`);}return {lines:a,media:n}}async function Be(e){let{client:t,quotedMessageId:a,mediaAllowHosts:n,log:o}=e;try{let r=await t.getMessageByMessageId(a),s=r.sender,l=s?.employee_code??"unknown",c=s?.email?`${l} (${s.email})`:l,i=await yt(r,{client:t,mediaAllowHosts:n,log:o});return {text:`[Quoted from ${c}: ${i.text}]`,media:i.media}}catch(r){return o(`seatalk: failed to resolve quoted message ${a}: ${String(r)}`),null}}async function Le(e){let{mediaUrls:t,client:a,to:n,threadId:o,isGroup:r,log:s}=e;await sendMediaWithLeadingCaption({mediaUrls:t,caption:"",send:async({mediaUrl:l})=>{await we({client:a,to:n,mediaUrl:l,threadId:o,isGroup:r});},onError:async({error:l,mediaUrl:c})=>{s(`seatalk: failed to send media ${c}: ${String(l)}`);}});}var St=U(()=>{Se();oe();});function Ue(e){let{send:t,chunkText:a,maxLength:n,joiner:o,idleFlushMs:r}=e,s="",l,c=Promise.resolve(),i=()=>{l&&(clearTimeout(l),l=void 0);},g=()=>{if(!s)return;let u=s;s="";let S=u.length>n?a(u,n):[u],h=async()=>{for(let w of S)await t(w);};c=c.then(h,h),c.catch(()=>{});},d=()=>{!r||r<=0||(i(),l=setTimeout(()=>{l=void 0,g();},r));};return {append:u=>{if(!u)return;if(i(),!s){s=u,d();return}let S=`${s}${o}${u}`;if(S.length>n){g(),s=u,d();return}s=S,d();},flush:async()=>{i(),g(),await c;},hasBuffered:()=>s.length>0}}var wt=U(()=>{});function kn(e,t,a){return a.some(n=>{let o=n.trim();return !!(o==="*"||o===e||t&&o.toLowerCase()===t.toLowerCase())})}function ve(e){let{cfg:t,event:a,client:n,runtime:o,accountId:r}=e,s=o?.log??console.log,l=o?.error??console.error,c=i=>i().catch(g=>l(`seatalk[${r}]: event error: ${String(g)}`));switch(a.event_type){case "message_from_bot_subscriber":c(()=>In({cfg:t,event:a,client:n,runtime:o,accountId:r}));break;case "new_mentioned_message_received_from_group_chat":case "new_message_received_from_thread":c(()=>$n({cfg:t,event:a,client:n,runtime:o,accountId:r}));break;case "new_bot_subscriber":{let i=a.event?.employee_code;s(`seatalk[${r}]: new subscriber: ${i}`);break}case "bot_added_to_group_chat":{let i=a.event?.group_id;s(`seatalk[${r}]: bot added to group: ${i}`);break}case "bot_removed_from_group_chat":{let i=a.event?.group_id;s(`seatalk[${r}]: bot removed from group: ${i}`);break}default:s(`seatalk[${r}]: unhandled event type: ${a.event_type}`);}}function Ct(e){let t=Date.now();if(t-Tt>wn){for(let[a,n]of Y)t-n>yn&&Y.delete(a);Tt=t;}if(Y.has(e))return false;if(Y.size>=Sn){let a=Y.keys().next().value;Y.delete(a);}return Y.set(e,t),true}function bn(e,t,a){return a?`${e}:dm:${t}:t:${a}`:`${e}:dm:${t}`}function Cn(e,t,a,n){return n?`${e}:grp:${t}:${a}:t:${n}`:`${e}:grp:${t}:${a}`}function _n(e,t){clearTimeout(t.timer);let a=Date.now()-t.firstEventAt,n=Tn-a;if(n<=0){Ge(e);return}let o=Math.min(_t,n);t.timer=setTimeout(()=>Ge(e),o);}function Ge(e){let t=_e.get(e);if(!t)return;_e.delete(e);let a=t.entries;if(a.length===0)return;a[0].kind==="dm"?vn(a,t.context).catch(r=>{(t.context.runtime?.error??console.error)(`seatalk[${t.context.accountId}]: flush error: ${String(r)}`);}):An(a,t.context).catch(r=>{(t.context.runtime?.error??console.error)(`seatalk[${t.context.accountId}]: group flush error: ${String(r)}`);});}function It(e,t,a){let n=_e.get(e);n||(n={entries:[],timer:setTimeout(()=>Ge(e),_t),firstEventAt:Date.now(),context:a},_e.set(e,n)),n.entries.push(t),_n(e,n);}async function vn(e,t){let{cfg:a,client:n,runtime:o,accountId:r}=t,s=o?.log??console.log,l=o?.error??console.error,c=e[0].parsedEvent,i=c.employee_code,g=c.email,d=A({cfg:a,accountId:r}),p=d.config,f=V(),u=p?.dmPolicy??"allowlist",S=(p?.allowFrom??[]).map(T=>String(T)),h=createChannelPairingController({core:f,channel:"seatalk",accountId:r}),w=u==="pairing"?await h.readAllowFromStore().catch(()=>[]):[],b=resolveDmGroupAccessWithLists({isGroup:false,dmPolicy:u,groupPolicy:"disabled",allowFrom:S,groupAllowFrom:[],storeAllowFrom:w,isSenderAllowed:T=>kn(i,g,T)});if(b.decision==="pairing"){(await h.issueChallenge({senderId:i,senderIdLine:`Your SeaTalk employee code: ${i}`,meta:g?{email:g}:void 0,onCreated:({code:y})=>{s(`seatalk[${r}]: pairing request sender=${i} code=${y}`);},sendPairingReply:async y=>{await H(n,i,y,1,c.message.thread_id);},onReplyError:y=>{s(`seatalk[${r}]: pairing reply failed for ${i}: ${String(y)}`);}})).created||s(`seatalk[${r}]: pairing already pending for ${i}`);return}if(b.decision!=="allow"){b.reasonCode===DM_GROUP_ACCESS_REASON.DM_POLICY_DISABLED?s(`seatalk[${r}]: blocked DM from ${i} (dmPolicy=disabled)`):s(`seatalk[${r}]: sender ${i} not in allowlist, dropping`);return}let $=p?.mediaAllowHosts,v={client:n,mediaAllowHosts:$,log:s},C=[],M=[];for(let{parsedEvent:T}of e){let y=T.message;switch(y.tag){case "text":(y.text?.plain_text||y.text?.content)&&C.push(y.text.plain_text??y.text.content??"");break;case "image":case "file":case "video":{let E=await de({message:y,client:n,mediaAllowHosts:$,log:s});E&&M.push(E);break}case "combined_forwarded_chat_history":{let E=y.combined_forwarded_chat_history?.content;if(E){let z=await ge(E,v);M.push(...z.media),C.push(z.lines.length>0?`[Forwarded messages]
4
- ${z.lines.join(`
5
- `)}`:"[Forwarded messages]");}break}}}let G=new Set,x=[];for(let{parsedEvent:T}of e){let y=T.message.quoted_message_id;if(!y||G.has(y))continue;G.add(y);let E=await Be({client:n,quotedMessageId:y,mediaAllowHosts:$,log:s});E&&(x.push(E.text),M.push(...E.media));}let re=De(M),R=C.join(`
6
- `);if(x.length>0){let T=x.join(`
7
- `);R=R?`${T}
8
- ${R}`:T;}if(!R&&M.length>0&&(R=M.map(T=>T.placeholder).join(" ")),!R&&M.length===0){s(`seatalk[${r}]: skipping empty message from ${i}`);return}let se=i+(g?` (${g})`:""),_=c.message.message_id,I=c.message.thread_id;try{let T=`seatalk:${i}`,y=i,E=f.channel.routing.resolveAgentRoute({cfg:a,channel:"seatalk",accountId:r,peer:{kind:"direct",id:i}}),z=R.replace(/\s+/g," ").slice(0,160);f.system.enqueueSystemEvent(`SeaTalk[${r}] DM from ${se}: ${z}`,{sessionKey:E.sessionKey,contextKey:`seatalk:message:${i}:${_}`});let Q=e[0].event.timestamp,ie=Q?new Date(Q*1e3):new Date,Ae=f.channel.reply.resolveEnvelopeFormatOptions(a),Ne=`${se}: ${R}`,fe=f.channel.reply.formatAgentEnvelope({channel:"SeaTalk",from:i,timestamp:ie,envelope:Ae,body:Ne}),Z={};I&&(Z.threadId=I);let le=c.message.quoted_message_id;le&&(Z.quotedMessageId=le);let me=f.channel.reply.finalizeInboundContext({Body:fe,BodyForAgent:R,RawBody:R,CommandBody:R,From:T,To:y,SessionKey:E.sessionKey,AccountId:E.accountId,ChatType:"direct",SenderName:se,SenderId:i,Provider:"seatalk",Surface:"seatalk",MessageSid:_,MessageThreadId:I||void 0,Timestamp:Q?Q*1e3:Date.now(),WasMentioned:!1,CommandAuthorized:!0,OriginatingChannel:"seatalk",OriginatingTo:y,...Object.keys(Z).length>0?{Metadata:Z}:{},...re});(d.config?.processingIndicator??"typing")==="typing"&&n.setSingleChatTyping(i,I).catch(B=>s(`seatalk[${r}]: typing failed: ${String(B)}`));let ee=p?.outboundCoalescing!==!1,he=B=>H(n,i,B,1,I),O=(B,L)=>f.channel.text.chunkMarkdownText(B,L),P=ee?Ue({send:he,chunkText:O,maxLength:Ce,joiner:`
3
+ `)}`:"[Forwarded messages]",media:n}}return {text:"[Forwarded messages]",media:n}}return {text:`<unsupported:${o??"unknown"}>`,media:n}}async function ye(e,t){let o=[],n=[];for(let a of e){if(Array.isArray(a)){let l=await ye(a,t);o.push(...l.lines),n.push(...l.media);continue}if(!a||typeof a!="object")continue;let r=a,i=Pn(r),s=await Pt(r,t);n.push(...s.media),s.text&&o.push(`${i}${s.text}`);}return {lines:o,media:n}}async function Ge(e){let{client:t,quotedMessageId:o,mediaAllowHosts:n}=e;try{let a=await t.getMessageByMessageId(o),r=a.sender,i=r?.employee_code??"unknown",s=r?.email?`${i} (${r.email})`:i,l=await Pt(a,{client:t,mediaAllowHosts:n});return {text:`[Quoted from ${s}: ${l.text}]`,media:l.media}}catch(a){return v("inbound").warn("resolve quoted message failed",{quotedMessageId:o,err:String(a)}),null}}async function je(e){let{mediaUrls:t,client:o,to:n,threadId:a,isGroup:r}=e;await sendMediaWithLeadingCaption({mediaUrls:t,caption:"",send:async({mediaUrl:i})=>{await ve({client:o,to:n,mediaUrl:i,threadId:a,isGroup:r});},onError:async({error:i,mediaUrl:s})=>{v("outbound").error("media delivery failed",{to:n,threadId:a,isGroup:r,mediaUrl:s,err:String(i)});}});}var xt=B(()=>{X();Ce();ce();});function He(e){let{send:t,chunkText:o,maxLength:n,joiner:a,idleFlushMs:r}=e,i="",s,l=Promise.resolve(),d=()=>{s&&(clearTimeout(s),s=void 0);},u=()=>{if(!i)return;let m=i;i="";let y=m.length>n?o(m,n):[m],h=async()=>{for(let T of y)await t(T);};l=l.then(h,h),l.catch(()=>{});},c=()=>{!r||r<=0||(d(),s=setTimeout(()=>{s=void 0,u();},r));};return {append:m=>{if(!m)return;if(d(),!i){i=m,c();return}let y=`${i}${a}${m}`;if(y.length>n){u(),i=m,c();return}i=y,c();},flush:async()=>{d(),u(),await l;},hasBuffered:()=>i.length>0}}var At=B(()=>{});function Rn(e,t,o){return o.some(n=>{let a=n.trim();return !!(a==="*"||a===e||t&&a.toLowerCase()===t.toLowerCase())})}function Pe(e){let{cfg:t,event:o,client:n,runtime:a,accountId:r}=e,i=v("event"),s=l=>l().catch(d=>i.error("event handler failed",{accountId:r,eventType:o.event_type,err:String(d)}));switch(o.event_type){case "message_from_bot_subscriber":s(()=>jn({cfg:t,event:o,client:n,runtime:a,accountId:r}));break;case "new_mentioned_message_received_from_group_chat":case "new_message_received_from_thread":s(()=>Hn({cfg:t,event:o,client:n,runtime:a,accountId:r}));break;case "new_bot_subscriber":{let l=o.event?.employee_code;i.info("new subscriber",{accountId:r,employeeCode:l});break}case "bot_added_to_group_chat":{let l=o.event?.group_id;i.info("bot added to group",{accountId:r,groupId:l});break}case "bot_removed_from_group_chat":{let l=o.event?.group_id;i.info("bot removed from group",{accountId:r,groupId:l});break}default:i.warn("unhandled event",{accountId:r,eventType:o.event_type});}}function Ot(e){let t=Date.now();if(t-Et>Dn){for(let[o,n]of te)t-n>On&&te.delete(o);Et=t;}if(te.has(e))return false;if(te.size>=$n){let o=te.keys().next().value;te.delete(o);}return te.set(e,t),true}function Fn(e,t,o){return o?`${e}:dm:${t}:t:${o}`:`${e}:dm:${t}`}function Ln(e,t,o,n){return n?`${e}:grp:${t}:${o}:t:${n}`:`${e}:grp:${t}:${o}`}function Un(e,t){clearTimeout(t.timer);let o=Date.now()-t.firstEventAt,n=Bn-o;if(n<=0){qe(e);return}let a=Math.min($t,n);t.timer=setTimeout(()=>qe(e),a);}function qe(e){let t=Ie.get(e);if(!t)return;Ie.delete(e);let o=t.entries;if(o.length===0)return;o[0].kind==="dm"?Gn(o,t.context).catch(r=>{v("inbound").error("dm flush failed",{accountId:t.context.accountId,err:String(r)});}):qn(o,t.context).catch(r=>{v("inbound").error("group flush failed",{accountId:t.context.accountId,err:String(r)});});}function Bt(e,t,o){let n=Ie.get(e);n||(n={entries:[],timer:setTimeout(()=>qe(e),$t),firstEventAt:Date.now(),context:o},Ie.set(e,n)),n.entries.push(t),Un(e,n);}async function Gn(e,t){let{cfg:o,client:n,accountId:a}=t,r=v("inbound"),i=e[0].parsedEvent,s=i.employee_code,l=i.email,d=x({cfg:o,accountId:a}),u=d.config,c=Q(),f=u?.dmPolicy??"allowlist",g=(u?.allowFrom??[]).map(b=>String(b)),m=createChannelPairingController({core:c,channel:"seatalk",accountId:a}),y=f==="pairing"?await m.readAllowFromStore().catch(()=>[]):[],h=resolveDmGroupAccessWithLists({isGroup:false,dmPolicy:f,groupPolicy:"disabled",allowFrom:g,groupAllowFrom:[],storeAllowFrom:y,isSenderAllowed:b=>Rn(s,l,b)});if(h.decision==="pairing"){(await m.issueChallenge({senderId:s,senderIdLine:`Your SeaTalk employee code: ${s}`,meta:l?{email:l}:void 0,onCreated:({code:w})=>{r.info("pairing challenge issued",{accountId:a,employeeCode:s,code:w});},sendPairingReply:async w=>{await H(n,s,w,1,i.message.thread_id);},onReplyError:w=>{r.warn("pairing reply failed",{accountId:a,employeeCode:s,err:String(w)});}})).created||r.info("pairing pending",{accountId:a,employeeCode:s});return}if(h.decision!=="allow"){let b=h.reasonCode===DM_GROUP_ACCESS_REASON.DM_POLICY_DISABLED?"dm policy disabled":"sender not in allowlist";r.warn("dm access denied",{accountId:a,employeeCode:s,reason:b});return}let T=u?.mediaAllowHosts,C={client:n,mediaAllowHosts:T},_=[],S=[];for(let{parsedEvent:b}of e){let w=b.message;switch(w.tag){case "text":(w.text?.plain_text||w.text?.content)&&_.push(w.text.plain_text??w.text.content??"");break;case "image":case "file":case "video":{let M=await me({message:w,client:n,mediaAllowHosts:T});M&&S.push(M);break}case "combined_forwarded_chat_history":{let M=w.combined_forwarded_chat_history?.content;if(M){let U=await ye(M,C);S.push(...U.media),_.push(U.lines.length>0?`[Forwarded messages]
4
+ ${U.lines.join(`
5
+ `)}`:"[Forwarded messages]");}break}}}let q=new Set,A=[];for(let{parsedEvent:b}of e){let w=b.message.quoted_message_id;if(!w||q.has(w))continue;q.add(w);let M=await Ge({client:n,quotedMessageId:w,mediaAllowHosts:T});M&&(A.push(M.text),S.push(...M.media));}let L=Le(S),O=_.join(`
6
+ `);if(A.length>0){let b=A.join(`
7
+ `);O=O?`${b}
8
+ ${O}`:b;}if(!O&&S.length>0&&(O=S.map(b=>b.placeholder).join(" ")),!O&&S.length===0){r.info("dm empty, skipping",{accountId:a,employeeCode:s});return}let de=s+(l?` (${l})`:""),I=i.message.message_id,P=i.message.thread_id;try{let b=`seatalk:${s}`,w=s,M=c.channel.routing.resolveAgentRoute({cfg:o,channel:"seatalk",accountId:a,peer:{kind:"direct",id:s}}),U=(u?.dmThreadSession??!0)&&!!P,ne=resolveThreadSessionKeys({baseSessionKey:M.sessionKey,threadId:U?P:void 0,parentSessionKey:U&&(u?.threadInheritParent??!0)?M.sessionKey:void 0}),Ee=O.replace(/\s+/g," ").slice(0,160);c.system.enqueueSystemEvent(`SeaTalk[${a}] DM from ${de}: ${Ee}`,{sessionKey:ne.sessionKey,contextKey:`seatalk:message:${s}:${I}`});let oe=e[0].event.timestamp,ue=oe?new Date(oe*1e3):new Date,Me=c.channel.reply.resolveEnvelopeFormatOptions(o),We=`${de}: ${O}`,ae=c.channel.reply.formatAgentEnvelope({channel:"SeaTalk",from:s,timestamp:ue,envelope:Me,body:We}),re={};P&&(re.threadId=P);let ge=i.message.quoted_message_id;ge&&(re.quotedMessageId=ge);let Se=c.channel.reply.finalizeInboundContext({Body:ae,BodyForAgent:O,RawBody:O,CommandBody:O,From:b,To:w,SessionKey:ne.sessionKey,ParentSessionKey:ne.parentSessionKey,AccountId:M.accountId,ChatType:"direct",SenderName:de,SenderId:s,Provider:"seatalk",Surface:"seatalk",MessageSid:I,MessageThreadId:P||void 0,Timestamp:oe?oe*1e3:Date.now(),WasMentioned:!1,CommandAuthorized:!0,OriginatingChannel:"seatalk",OriginatingTo:w,...Object.keys(re).length>0?{Metadata:re}:{},...L});(d.config?.processingIndicator??"typing")==="typing"&&n.setSingleChatTyping(s,P).catch(G=>r.warn("dm typing failed",{accountId:a,employeeCode:s,err:String(G)}));let pe=u?.outboundCoalescing!==!1,J=G=>H(n,s,G,1,P),we=(G,D)=>c.channel.text.chunkMarkdownText(G,D),E=pe?He({send:J,chunkText:we,maxLength:_e,joiner:`
9
9
 
10
- `,idleFlushMs:vt}):null,te=f.channel.reply.createReplyDispatcherWithTyping({humanDelay:f.channel.reply.resolveHumanDelayConfig(a,E.agentId),deliver:async B=>{let L=resolveSendableOutboundReplyParts(B);if(!(!L.hasText&&!L.hasMedia)){if(L.hasText)if(s(`seatalk[${r}]: inline deliver DM to ${i} threadId=${I||"none"}`),P)P.append(L.trimmedText);else {let jt=O(L.trimmedText,Ce);for(let Ht of jt)await he(Ht);}L.hasMedia&&(P&&await P.flush(),await Le({mediaUrls:L.mediaUrls,client:n,to:i,threadId:I,isGroup:!1,log:s}));}},onError:B=>{l(`seatalk[${r}]: reply delivery failed: ${String(B)}`);}}),Ee={agentId:E.agentId,...te.replyOptions};s(`seatalk[${r}]: dispatching to agent (session=${E.sessionKey})`);try{let{queuedFinal:B,counts:L}=await f.channel.reply.dispatchReplyFromConfig({ctx:me,cfg:a,dispatcher:te.dispatcher,replyOptions:Ee});s(`seatalk[${r}]: dispatch complete (queuedFinal=${B}, counts=${JSON.stringify(L)})`);}finally{te.markDispatchIdle(),P&&(await te.dispatcher.waitForIdle(),await P.flush());}}catch(T){l(`seatalk[${r}]: failed to dispatch message: ${String(T)}`);}}async function In(e){let{cfg:t,event:a,client:n,runtime:o,accountId:r}=e,s=o?.log??console.log;if(!Ct(`${r}:${a.event_id}`)){s(`seatalk[${r}]: skipping duplicate event ${a.event_id}`);return}let l=a.event;if(!l?.employee_code||!l?.message){s(`seatalk[${r}]: malformed message event, skipping`);return}s(`seatalk[${r}]: received ${l.message.tag} from ${l.employee_code} (threadId=${l.message.thread_id||"none"})`);let c=bn(r,l.employee_code,l.message.thread_id);It(c,{kind:"dm",event:a,parsedEvent:l},{cfg:t,client:n,runtime:o,accountId:r});}async function $n(e){let{cfg:t,event:a,client:n,runtime:o,accountId:r}=e,s=o?.log??console.log;if(!Ct(`${r}:${a.event_id}`)){s(`seatalk[${r}]: skipping duplicate group event ${a.event_id}`);return}let l=a.event,c=l?.group_id,i=l?.message,g=i?.sender;if(!c||!i||!g?.employee_code){s(`seatalk[${r}]: malformed group message event, skipping`);return}if(g.sender_type===2){s(`seatalk[${r}]: ignoring bot message in group ${c}`);return}let d=g.employee_code,p=g.email,f=i.thread_id;s(`seatalk[${r}]: group ${c} ${i.tag} from ${d} (event=${a.event_type})`);let S=A({cfg:t,accountId:r}).config,h=ht({groupPolicy:S?.groupPolicy??"disabled",groupAllowFrom:S?.groupAllowFrom,groupSenderAllowFrom:S?.groupSenderAllowFrom,groupId:c,senderEmployeeCode:d,senderEmail:p});if(!h.allowed){s(`seatalk[${r}]: group access denied: ${h.reason}`);return}let w=Cn(r,c,d,f);It(w,{kind:"group",event:a,groupEvent:l,groupId:c,eventType:a.event_type},{cfg:t,client:n,runtime:o,accountId:r});}async function An(e,t){let{cfg:a,client:n,runtime:o,accountId:r}=t,s=o?.log??console.log,l=o?.error??console.error,c=e[0],i=c.groupId,g=c.groupEvent.message,d=g.sender,p=d.employee_code,f=d.email,u=g.thread_id,h=A({cfg:a,accountId:r}).config,w=h?.mediaAllowHosts,b={client:n,mediaAllowHosts:w,log:s},$=[],v=[];for(let{groupEvent:_}of e){let I=_.message;switch(I.tag){case "text":(I.text?.plain_text||I.text?.content)&&$.push(I.text.plain_text??I.text.content??"");break;case "image":case "file":case "video":{let T=await de({message:I,client:n,mediaAllowHosts:w,log:s});T&&v.push(T);break}case "combined_forwarded_chat_history":{let T=I.combined_forwarded_chat_history?.content;if(T){let y=await ge(T,b);v.push(...y.media),$.push(y.lines.length>0?`[Forwarded messages]
11
- ${y.lines.join(`
12
- `)}`:"[Forwarded messages]");}break}}}let C=c.groupEvent.message.quoted_message_id,M=null;if(C){let _=await Be({client:n,quotedMessageId:C,mediaAllowHosts:w,log:s});_&&(M=_.text,v.push(..._.media));}let G=De(v),x=$.join(`
13
- `);if(M&&(x=x?`${M}
14
- ${x}`:M),!x&&v.length>0&&(x=v.map(_=>_.placeholder).join(" ")),!x&&v.length===0){s(`seatalk[${r}]: skipping empty group message from ${p} in ${i}`);return}let re=p+(f?` (${f})`:""),R=g.message_id,se=e.some(_=>_.eventType==="new_mentioned_message_received_from_group_chat");try{let _=V(),I=_.channel.routing.resolveAgentRoute({cfg:a,channel:"seatalk",accountId:r,peer:{kind:"group",id:i}}),T=x.replace(/\s+/g," ").slice(0,160);_.system.enqueueSystemEvent(`SeaTalk[${r}] Group(${i}) from ${re}: ${T}`,{sessionKey:I.sessionKey,contextKey:`seatalk:group:${i}:${R}`});let y=c.groupEvent.message.message_sent_time,E=y?new Date(y*1e3):new Date,z=_.channel.reply.resolveEnvelopeFormatOptions(a),Q=_.channel.reply.formatAgentEnvelope({channel:"SeaTalk",from:p,timestamp:E,envelope:z,body:`${re}: ${x}`}),ie={groupId:i};u&&(ie.threadId=u),C&&(ie.quotedMessageId=C);let Ae=_.channel.reply.finalizeInboundContext({Body:Q,BodyForAgent:x,RawBody:x,CommandBody:x,From:`seatalk:${p}`,To:`group:${i}`,SessionKey:I.sessionKey,AccountId:I.accountId,ChatType:"group",SenderName:re,SenderId:p,Provider:"seatalk",Surface:"seatalk",MessageSid:R,MessageThreadId:u||void 0,Timestamp:y?y*1e3:Date.now(),WasMentioned:se,CommandAuthorized:!0,OriginatingChannel:"seatalk",OriginatingTo:`group:${i}`,Metadata:ie,...G});(h?.processingIndicator??"typing")==="typing"&&n.setGroupChatTyping(i,u).catch(O=>s(`seatalk[${r}]: group typing failed: ${String(O)}`));let fe=u||void 0,Z=h?.outboundCoalescing!==!1,le=O=>ae(n,i,O,1,fe),me=(O,P)=>_.channel.text.chunkMarkdownText(O,P),W=Z?Ue({send:le,chunkText:me,maxLength:Ce,joiner:`
10
+ `,idleFlushMs:Dt}):null,R=v("outbound"),se=c.channel.reply.createReplyDispatcherWithTyping({humanDelay:c.channel.reply.resolveHumanDelayConfig(o,M.agentId),deliver:async G=>{let D=resolveSendableOutboundReplyParts(G);if(!(!D.hasText&&!D.hasMedia)){if(D.hasText)if(R.info("dm inline deliver",{accountId:a,employeeCode:s,threadId:P,kind:"text"}),E)E.append(D.trimmedText);else {let Yt=we(D.trimmedText,_e);for(let Qt of Yt)await J(Qt);}D.hasMedia&&(R.info("dm inline deliver",{accountId:a,employeeCode:s,threadId:P,kind:"media",count:D.mediaUrls.length}),E&&await E.flush(),await je({mediaUrls:D.mediaUrls,client:n,to:s,threadId:P,isGroup:!1}));}},onError:G=>{R.error("dm reply delivery failed",{accountId:a,employeeCode:s,err:String(G)});}}),Re={agentId:M.agentId,...se.replyOptions};r.info("dm dispatching to agent",{accountId:a,employeeCode:s,sessionKey:ne.sessionKey});try{let{queuedFinal:G,counts:D}=await c.channel.reply.dispatchReplyFromConfig({ctx:Se,cfg:o,dispatcher:se.dispatcher,replyOptions:Re});r.info("dm dispatch complete",{accountId:a,employeeCode:s,queuedFinal:G,counts:D});}finally{se.markDispatchIdle(),E&&(await se.dispatcher.waitForIdle(),await E.flush());}}catch(b){r.error("dm dispatch failed",{accountId:a,employeeCode:s,err:String(b)});}}async function jn(e){let{cfg:t,event:o,client:n,runtime:a,accountId:r}=e,i=v("inbound");if(!Ot(`${r}:${o.event_id}`)){i.info("duplicate event skipped",{accountId:r,eventId:o.event_id});return}let s=o.event;if(!s?.employee_code||!s?.message){i.warn("malformed dm event",{accountId:r});return}i.info("dm received",{accountId:r,employeeCode:s.employee_code,tag:s.message.tag,threadId:s.message.thread_id});let l=Fn(r,s.employee_code,s.message.thread_id);Bt(l,{kind:"dm",event:o,parsedEvent:s},{cfg:t,client:n,runtime:a,accountId:r});}async function Hn(e){let{cfg:t,event:o,client:n,runtime:a,accountId:r}=e,i=v("inbound");if(!Ot(`${r}:${o.event_id}`)){i.info("duplicate group event skipped",{accountId:r,eventId:o.event_id});return}let s=o.event,l=s?.group_id,d=s?.message,u=d?.sender;if(!l||!d||!u?.employee_code){i.warn("malformed group event",{accountId:r});return}if(u.sender_type===2){i.info("ignoring bot self message",{accountId:r,groupId:l});return}let c=u.employee_code,f=u.email,g=d.thread_id;i.info("group received",{accountId:r,groupId:l,employeeCode:c,tag:d.tag,eventType:o.event_type,threadId:g});let y=x({cfg:t,accountId:r}).config,h=_t({groupPolicy:y?.groupPolicy??"disabled",groupAllowFrom:y?.groupAllowFrom,groupSenderAllowFrom:y?.groupSenderAllowFrom,groupId:l,senderEmployeeCode:c,senderEmail:f});if(!h.allowed){i.warn("group access denied",{accountId:r,groupId:l,employeeCode:c,reason:h.reason});return}let T=Ln(r,l,c,g);Bt(T,{kind:"group",event:o,groupEvent:s,groupId:l,eventType:o.event_type},{cfg:t,client:n,runtime:a,accountId:r});}async function qn(e,t){let{cfg:o,client:n,accountId:a}=t,r=v("inbound"),i=e[0],s=i.groupId,l=i.groupEvent.message,d=l.sender,u=d.employee_code,c=d.email,f=l.thread_id,m=x({cfg:o,accountId:a}).config,y=m?.mediaAllowHosts,h={client:n,mediaAllowHosts:y},T=[],C=[];for(let{groupEvent:I}of e){let P=I.message;switch(P.tag){case "text":(P.text?.plain_text||P.text?.content)&&T.push(P.text.plain_text??P.text.content??"");break;case "image":case "file":case "video":{let b=await me({message:P,client:n,mediaAllowHosts:y});b&&C.push(b);break}case "combined_forwarded_chat_history":{let b=P.combined_forwarded_chat_history?.content;if(b){let w=await ye(b,h);C.push(...w.media),T.push(w.lines.length>0?`[Forwarded messages]
11
+ ${w.lines.join(`
12
+ `)}`:"[Forwarded messages]");}break}}}let _=i.groupEvent.message.quoted_message_id,S=null;if(_){let I=await Ge({client:n,quotedMessageId:_,mediaAllowHosts:y});I&&(S=I.text,C.push(...I.media));}let q=Le(C),A=T.join(`
13
+ `);if(S&&(A=A?`${S}
14
+ ${A}`:S),!A&&C.length>0&&(A=C.map(I=>I.placeholder).join(" ")),!A&&C.length===0){r.info("group empty, skipping",{accountId:a,groupId:s,employeeCode:u});return}let L=u+(c?` (${c})`:""),O=l.message_id,de=e.some(I=>I.eventType==="new_mentioned_message_received_from_group_chat");try{let I=Q(),P=I.channel.routing.resolveAgentRoute({cfg:o,channel:"seatalk",accountId:a,peer:{kind:"group",id:s}}),b=(m?.groupThreadSession??!0)&&!!f,w=resolveThreadSessionKeys({baseSessionKey:P.sessionKey,threadId:b?f:void 0,parentSessionKey:b&&(m?.threadInheritParent??!0)?P.sessionKey:void 0}),M=A.replace(/\s+/g," ").slice(0,160);I.system.enqueueSystemEvent(`SeaTalk[${a}] Group(${s}) from ${L}: ${M}`,{sessionKey:w.sessionKey,contextKey:`seatalk:group:${s}:${O}`});let U=i.groupEvent.message.message_sent_time,ne=U?new Date(U*1e3):new Date,Ee=I.channel.reply.resolveEnvelopeFormatOptions(o),oe=I.channel.reply.formatAgentEnvelope({channel:"SeaTalk",from:u,timestamp:ne,envelope:Ee,body:`${L}: ${A}`}),ue={groupId:s};f&&(ue.threadId=f),_&&(ue.quotedMessageId=_);let Me=I.channel.reply.finalizeInboundContext({Body:oe,BodyForAgent:A,RawBody:A,CommandBody:A,From:`seatalk:${u}`,To:`group:${s}`,SessionKey:w.sessionKey,ParentSessionKey:w.parentSessionKey,AccountId:P.accountId,ChatType:"group",SenderName:L,SenderId:u,Provider:"seatalk",Surface:"seatalk",MessageSid:O,MessageThreadId:f||void 0,Timestamp:U?U*1e3:Date.now(),WasMentioned:de,CommandAuthorized:!0,OriginatingChannel:"seatalk",OriginatingTo:`group:${s}`,Metadata:ue,...q});(m?.processingIndicator??"typing")==="typing"&&n.setGroupChatTyping(s,f).catch(E=>r.warn("group typing failed",{accountId:a,groupId:s,err:String(E)}));let ae=f||void 0,re=m?.outboundCoalescing!==!1,ge=E=>le(n,s,E,1,ae),Se=(E,R)=>I.channel.text.chunkMarkdownText(E,R),V=re?He({send:ge,chunkText:Se,maxLength:_e,joiner:`
15
15
 
16
- `,idleFlushMs:vt}):null,ee=_.channel.reply.createReplyDispatcherWithTyping({humanDelay:_.channel.reply.resolveHumanDelayConfig(a,I.agentId),deliver:async O=>{let P=resolveSendableOutboundReplyParts(O);if(!(!P.hasText&&!P.hasMedia)){if(P.hasText)if(W)W.append(P.trimmedText);else {let te=me(P.trimmedText,Ce);for(let Ee of te)await le(Ee);}P.hasMedia&&(W&&await W.flush(),await Le({mediaUrls:P.mediaUrls,client:n,to:i,threadId:fe,isGroup:!0,log:s}));}},onError:O=>{l(`seatalk[${r}]: group reply delivery failed: ${String(O)}`);}}),he={agentId:I.agentId,...ee.replyOptions};s(`seatalk[${r}]: dispatching group message (session=${I.sessionKey})`);try{let{queuedFinal:O,counts:P}=await _.channel.reply.dispatchReplyFromConfig({ctx:Ae,cfg:a,dispatcher:ee.dispatcher,replyOptions:he});s(`seatalk[${r}]: group dispatch complete (queuedFinal=${O}, counts=${JSON.stringify(P)})`);}finally{ee.markDispatchIdle(),W&&(await ee.dispatcher.waitForIdle(),await W.flush());}}catch(_){l(`seatalk[${r}]: failed to dispatch group message: ${String(_)}`);}}var yn,Sn,wn,Y,Tt,_t,Tn,Ce,vt,_e,je=U(()=>{kt();N();St();Se();wt();ce();oe();yn=1800*1e3,Sn=1e3,wn=300*1e3,Y=new Map,Tt=Date.now();_t=1500,Tn=5e3,Ce=4e3,vt=1e3,_e=new Map;});var Pt={};ze(Pt,{connectSeaTalkRelay:()=>Rn});function xn(e,t){return new Promise(a=>{let n=()=>{clearTimeout(o),a();},o=setTimeout(()=>{t?.removeEventListener("abort",n),a();},e);t?.addEventListener("abort",n,{once:true});})}async function Et(e){let{cfg:t,account:a,relayUrl:n,runtime:o,abortSignal:r}=e,{accountId:s}=a,l=o?.log??console.log,c=o?.error??console.error,i=q(a.config)?.signingSecret;if(!a.appId||!a.appSecret||!i)throw new Error(`SeaTalk account "${s}" missing credentials for relay mode`);let g=F(a);if(!g)throw new Error(`SeaTalk client not available for account "${s}"`);let d=$t;for(;!r?.aborted;){try{await new Promise((p,f)=>{if(r?.aborted){p();return}l(`seatalk[${s}]: connecting to relay ${n}...`);let u=new En(n),S,h=()=>{S&&(clearTimeout(S),S=void 0);},w=()=>{h(),S=setTimeout(()=>{c(`seatalk[${s}]: relay silent for ${At}ms, terminating`),u.terminate();},At);},b=()=>{h(),u.close(),p();};r?.addEventListener("abort",b,{once:!0}),u.on("upgrade",v=>{v.socket.setKeepAlive(!0,6e4);}),u.on("open",()=>{w(),l(`seatalk[${s}]: relay connected, authenticating...`),u.send(JSON.stringify({type:"auth",appId:a.appId,appSecret:a.appSecret,signingSecret:i}));});let $=!1;u.on("message",v=>{w();let C;try{C=JSON.parse(String(v));}catch{c(`seatalk[${s}]: relay sent invalid JSON`);return}if(!$){C.type==="auth_ok"?($=!0,d=$t,l(`seatalk[${s}]: relay authenticated`)):C.type==="auth_fail"&&(c(`seatalk[${s}]: relay auth failed: ${C.error}`),u.close(),f(new Error(`Relay auth failed: ${C.error}`)));return}switch(C.type){case "event":C.event&&g&&ve({cfg:t,event:C.event,client:g,runtime:o,accountId:s});break;case "ping":u.send(JSON.stringify({type:"pong"}));break;case "replaced":l(`seatalk[${s}]: connection replaced by another instance`),u.close(),p();return;default:l(`seatalk[${s}]: unknown relay message type: ${C.type}`);}}),u.on("close",(v,C)=>{h(),r?.removeEventListener("abort",b),$&&l(`seatalk[${s}]: relay disconnected (code=${v}, reason=${String(C)})`),p();}),u.on("error",v=>{h(),r?.removeEventListener("abort",b),c(`seatalk[${s}]: relay connection error: ${String(v)}`),p();});});}catch(p){let f=String(p);if(f.includes("Relay auth failed"))throw p;c(`seatalk[${s}]: relay error: ${f}`);}if(r?.aborted)break;l(`seatalk[${s}]: reconnecting in ${d}ms...`),await xn(d,r),d=Math.min(d*Mn,Pn);}}async function Rn(e){let t=e.config;if(!t)throw new Error("Config is required for SeaTalk relay client");let a=e.runtime?.log??console.log;if(e.accountId){let o=A({cfg:t,accountId:e.accountId});if(!o.enabled||!o.configured)throw new Error(`SeaTalk account "${e.accountId}" not configured or disabled`);return Et({cfg:t,account:o,relayUrl:e.relayUrl,runtime:e.runtime,abortSignal:e.abortSignal})}let n=ne(t);if(n.length===0)throw new Error("No enabled SeaTalk accounts configured");a(`seatalk: connecting ${n.length} account(s) to relay: ${n.map(o=>o.accountId).join(", ")}`),await Promise.all(n.map(o=>Et({cfg:t,account:o,relayUrl:e.relayUrl,runtime:e.runtime,abortSignal:e.abortSignal})));}var $t,Pn,Mn,At,Mt=U(()=>{N();je();K();$t=1e3,Pn=3e4,Mn=2,At=75e3;});var Dt={};ze(Dt,{monitorSeaTalkProvider:()=>Ot});function On(e,t,a){let n=Buffer.from(t,"latin1"),o=$e.createHash("sha256").update(Buffer.concat([e,n])).digest("hex");try{return $e.timingSafeEqual(Buffer.from(o,"hex"),Buffer.from(a,"hex"))}catch{return false}}function Fn(e){return new Promise((t,a)=>{let n=0,o=[];e.on("data",r=>{if(n+=r.length,n>Dn){e.destroy(new Ie);return}o.push(r);}),e.on("end",()=>t(Buffer.concat(o))),e.on("error",a);})}async function xt(e){let{cfg:t,account:a,runtime:n,abortSignal:o}=e,{accountId:r}=a,s=n?.log??console.log,l=n?.error??console.error,c=a.webhookPort,i=a.webhookPath,g=q(a.config)?.signingSecret;if(!g)throw new Error(`SeaTalk account "${r}" missing signingSecret`);let d=F(a);if(!d)throw new Error(`SeaTalk client not available for account "${r}"`);s(`seatalk[${r}]: starting webhook server on port ${c}, path ${i}...`);let p=Rt.createServer();return p.on("request",async(f,u)=>{let S=new URL(f.url??"/",`http://localhost:${c}`).pathname;if(f.method!=="POST"||S!==i){u.writeHead(404),u.end("Not Found");return}try{let h=await Fn(f),w=f.headers.signature;if(!w||!On(h,g,w)){s(`seatalk[${r}]: signature verification failed`),u.writeHead(403),u.end("Forbidden");return}let b=JSON.parse(h.toString("utf-8"));if(b.event_type==="event_verification"){let $=b.event?.seatalk_challenge;u.writeHead(200,{"Content-Type":"application/json"}),u.end(JSON.stringify({seatalk_challenge:$})),s(`seatalk[${r}]: URL verification challenge responded`);return}u.writeHead(200),u.end("OK"),ve({cfg:t,event:b,client:d,runtime:n,accountId:r});}catch(h){l(`seatalk[${r}]: request processing error: ${String(h)}`),u.headersSent||(h instanceof Ie?(u.writeHead(413),u.end("Payload Too Large")):(u.writeHead(500),u.end("Internal Server Error")));}}),new Promise((f,u)=>{let S=()=>{p.close();},h=()=>{s(`seatalk[${r}]: abort signal received, stopping webhook server`),S(),f();};if(o?.aborted){S(),f();return}o?.addEventListener("abort",h,{once:true}),p.listen(c,()=>{s(`seatalk[${r}]: webhook server listening on port ${c}`);}),p.on("error",w=>{l(`seatalk[${r}]: webhook server error: ${w}`),o?.removeEventListener("abort",h),u(w);});})}async function Ot(e={}){let t=e.config;if(!t)throw new Error("Config is required for SeaTalk monitor");let a=e.runtime?.log??console.log;if(e.accountId){let o=A({cfg:t,accountId:e.accountId});if(!o.enabled||!o.configured)throw new Error(`SeaTalk account "${e.accountId}" not configured or disabled`);return xt({cfg:t,account:o,runtime:e.runtime,abortSignal:e.abortSignal})}let n=ne(t);if(n.length===0)throw new Error("No enabled SeaTalk accounts configured");a(`seatalk: starting ${n.length} account(s): ${n.map(o=>o.accountId).join(", ")}`),await Promise.all(n.map(o=>xt({cfg:t,account:o,runtime:e.runtime,abortSignal:e.abortSignal})));}var Dn,Ie,He=U(()=>{N();je();K();Dn=1024*1024,Ie=class extends Error{constructor(){super("Request body too large"),this.name="PayloadTooLargeError";}};});N();K();var Je=z.enum(["open","allowlist","pairing"]),Xe=z.enum(["webhook","relay"]),Ye=z.enum(["disabled","allowlist","open"]),Qe=z.enum(["typing","off"]),Vt=z.object({groupInfo:z.boolean().optional().default(true),groupHistory:z.boolean().optional().default(true),groupList:z.boolean().optional().default(true),threadHistory:z.boolean().optional().default(true),getMessage:z.boolean().optional().default(true)}).strict(),Jt=z.object({enabled:z.boolean().optional(),appId:z.string().optional(),appSecret:z.string().optional(),signingSecret:z.string().optional(),mode:Xe.optional(),relayUrl:z.string().optional(),webhookPort:z.number().int().positive().optional(),webhookPath:z.string().optional(),dmPolicy:Je.optional(),allowFrom:z.array(z.string()).optional(),groupPolicy:Ye.optional(),groupAllowFrom:z.array(z.string()).optional(),groupSenderAllowFrom:z.array(z.string()).optional(),processingIndicator:Qe.optional(),mediaAllowHosts:z.array(z.string()).optional(),outboundCoalescing:z.boolean().optional()}).strict(),Ze=z.object({enabled:z.boolean().optional(),appId:z.string().optional(),appSecret:z.string().optional(),signingSecret:z.string().optional(),mode:Xe.optional().default("webhook"),relayUrl:z.string().optional(),webhookPort:z.number().int().positive().optional().default(8080),webhookPath:z.string().optional().default("/callback"),dmPolicy:Je.optional().default("allowlist"),allowFrom:z.array(z.string()).optional(),groupPolicy:Ye.optional().default("disabled"),groupAllowFrom:z.array(z.string()).optional(),groupSenderAllowFrom:z.array(z.string()).optional(),processingIndicator:Qe.optional().default("typing"),mediaAllowHosts:z.array(z.string()).optional(),outboundCoalescing:z.boolean().optional().default(true),tools:Vt.optional(),accounts:z.record(z.string(),Jt.optional()).optional()}).strict().superRefine((e,t)=>{e.dmPolicy==="open"&&((e.allowFrom??[]).some(o=>o.trim()==="*")||t.addIssue({code:z.ZodIssueCode.custom,path:["allowFrom"],message:'channels.seatalk.dmPolicy="open" requires channels.seatalk.allowFrom to include "*"'}));});N();K();ce();oe();var rt="group:";function st(e){let t=e.trim();return t||null}function Te(e){return e.startsWith(rt)}function be(e){return e.slice(rt.length)}function X(e){return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e.trim())}function it(e){let t=e.trim();return t?X(t)?true:Te(t)?be(t).length>0:/^[a-zA-Z0-9_-]+$/.test(t):false}function lt(e,t){let a=A({cfg:e,accountId:t}),n=F(a);if(!n)throw new Error(`SeaTalk client not available for account ${a.accountId}`);return n}async function ct(e,t){if(!X(t))return t;let n=(await e.getEmployeeCodeByEmail([t])).find(o=>o.employeeCode&&o.status===2);if(n?.employeeCode)return n.employeeCode;throw new Error(`No active SeaTalk employee found for email: ${t}`)}function dt(e){if(e!=null)return String(e)}var ut={deliveryMode:"direct",chunker:(e,t)=>V().channel.text.chunkMarkdownText(e,t),chunkerMode:"markdown",textChunkLimit:4e3,sendText:async({cfg:e,to:t,text:a,accountId:n,threadId:o})=>{let r=lt(e,n??void 0),s=dt(o);if(Te(t)){let c=be(t);return await ae(r,c,a,1,s),{channel:"seatalk",messageId:"",chatId:t}}let l=await ct(r,t);return await H(r,l,a,1,s),{channel:"seatalk",messageId:"",chatId:l}},sendMedia:async({cfg:e,to:t,text:a,mediaUrl:n,accountId:o,threadId:r})=>{let s=lt(e,o??void 0),l=dt(r),c=Te(t),i=c?be(t):await ct(s,t);if(a?.trim()&&(c?await ae(s,i,a,1,l):await H(s,i,a,1,l)),n)try{await we({client:s,to:i,mediaUrl:n,threadId:l,isGroup:c});}catch(g){let d=`[Media send failed: ${g instanceof Error?g.message:String(g)}]`;c?await ae(s,i,d,2,l):await H(s,i,d,2,l);}return {channel:"seatalk",messageId:"",chatId:c?t:i}}};K();async function ue(e){if(!e?.appId||!e?.appSecret)return {ok:false,error:"missing credentials (appId, appSecret)"};try{let t=xe(e.appId,e.appSecret),a=Date.now();await t.getAccessToken();let n=Date.now()-a;return {ok:!0,appId:e.appId,latencyMs:n}}catch(t){return {ok:false,appId:e.appId,error:t instanceof Error?t.message:String(t)}}}oe();N();var pt="seatalk";function Fe(e){return e.split(/[\n,;]+/g).map(t=>t.trim()).filter(Boolean)}async function ft(e){let{cfg:t,prompter:a}=e,n=t.channels?.seatalk?.allowFrom??[];for(await a.note(["Allowlist SeaTalk DMs by email or employee_code.","Examples:","- alice@company.com","- 12345678"].join(`
17
- `),"SeaTalk allowlist");;){let o=await a.text({message:"SeaTalk allowFrom (emails or employee_codes)",placeholder:"alice@company.com, 12345678",initialValue:n[0]?String(n[0]):void 0,validate:l=>String(l??"").trim()?void 0:"Required"}),r=Fe(String(o));if(r.length===0){await a.note("Enter at least one user.","SeaTalk allowlist");continue}let s=mergeAllowFromEntries(n.map(l=>String(l)),r);return {...t,channels:{...t.channels,seatalk:{...t.channels?.seatalk,allowFrom:s}}}}}var cn=createTopLevelChannelDmPolicy({label:"SeaTalk",channel:pt,policyKey:"channels.seatalk.dmPolicy",allowFromKey:"channels.seatalk.allowFrom",getCurrent:e=>e.channels?.seatalk?.dmPolicy??"allowlist",promptAllowFrom:async({cfg:e,prompter:t})=>ft({cfg:e,prompter:t})});async function gt(e){let t=String(await e.text({message:"Enter SeaTalk App ID",validate:o=>o?.trim()?void 0:"Required"})).trim(),a=String(await e.text({message:"Enter SeaTalk App Secret",validate:o=>o?.trim()?void 0:"Required"})).trim(),n=String(await e.text({message:"Enter SeaTalk Signing Secret",validate:o=>o?.trim()?void 0:"Required"})).trim();return {appId:t,appSecret:a,signingSecret:n}}async function dn(e){await e.note(["1) Go to SeaTalk Open Platform (open.seatalk.io)","2) Create a Bot App","3) Get App ID and App Secret from Basic Info & Credentials","4) Get Signing Secret from Event Callback settings","5) Enable Bot capability and set status to Online",'6) Enable "Send Message to Bot User" permission'].join(`
18
- `),"SeaTalk credentials");}var mt={channel:pt,status:createStandardChannelSetupStatus({channelLabel:"SeaTalk",configuredLabel:"configured",unconfiguredLabel:"needs app credentials",configuredHint:"configured",unconfiguredHint:"needs app creds",configuredScore:2,unconfiguredScore:0,resolveConfigured:({cfg:e})=>{let t=e.channels?.seatalk;return !!q(t)}}),credentials:[],finalize:async({cfg:e,prompter:t,forceAllowFrom:a})=>{let n=e,o=n.channels?.seatalk,r=q(o),s=!!(o?.appId?.trim()&&o?.appSecret?.trim()&&o?.signingSecret?.trim()),l=null,c=null,i=null;if(r||await dn(t),s?await t.confirm({message:"SeaTalk credentials already configured. Keep them?",initialValue:true})||({appId:l,appSecret:c,signingSecret:i}=await gt(t)):{appId:l,appSecret:c,signingSecret:i}=await gt(t),l&&c&&i){n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,enabled:true,appId:l,appSecret:c,signingSecret:i,dmPolicy:o?.dmPolicy??"allowlist"}}};try{let h=await ue({appId:l,appSecret:c});h.ok?await t.note(`Connected successfully (latency: ${h.latencyMs}ms)`,"SeaTalk connection test"):await t.note(`Connection failed: ${h.error??"unknown error"}`,"SeaTalk connection test");}catch(h){await t.note(`Connection test failed: ${String(h)}`,"SeaTalk connection test");}await t.note(["Important reminders:",'- Bot App must be set to "Online" status in SeaTalk Open Platform','- "Send Message to Bot User" permission must be enabled',"- Configure the callback URL in Event Callback settings"].join(`
19
- `),"SeaTalk setup");}let g=n.channels?.seatalk?.mode??"webhook",d=await t.select({message:"Gateway mode",options:[{value:"webhook",label:"Webhook \u2014 receive event callbacks directly (default)"},{value:"relay",label:"Relay \u2014 connect to a relay service as client"}],initialValue:g}),p=String(d);if(n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,mode:p}}},p==="relay"){let h=n.channels?.seatalk?.relayUrl??"",w=await t.text({message:"Relay WebSocket URL",placeholder:"wss://relay.example.com/ws",initialValue:h||void 0,validate:$=>{let v=String($??"").trim();if(!v)return "Required";if(!v.startsWith("ws://")&&!v.startsWith("wss://"))return "Must be a ws:// or wss:// URL"}}),b=String(w).trim();b.startsWith("ws://")&&await t.note("ws:// transmits credentials (appSecret, signingSecret) unencrypted. Consider using wss:// for production.","Security warning"),n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,relayUrl:b}}};}else {let h=n.channels?.seatalk?.webhookPort??8080,w=await t.text({message:"Webhook port",initialValue:String(h),validate:M=>{let G=Number(M);return G>0&&G<65536?void 0:"Enter a valid port number (1-65535)"}}),b=Number(w);b&&b!==h&&(n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,webhookPort:b}}});let $=n.channels?.seatalk?.webhookPath??"/callback",v=await t.text({message:"Webhook path",initialValue:$,validate:M=>{let G=String(M??"").trim();if(!G)return "Required";if(!G.startsWith("/"))return "Path must start with /"}}),C=String(v??$).trim();C&&C!==$&&(n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,webhookPath:C}}});}let f=await t.select({message:"Group chat policy",options:[{value:"disabled",label:"Disabled \u2014 ignore all group messages (default)"},{value:"allowlist",label:"Allowlist \u2014 respond only in specific groups"},{value:"open",label:"Open \u2014 respond in all groups the bot joins"}],initialValue:n.channels?.seatalk?.groupPolicy??"disabled"}),u=String(f);if(n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,groupPolicy:u}}},u==="allowlist"){let h=n.channels?.seatalk?.groupAllowFrom??[],w=await t.text({message:"Allowed group IDs (comma-separated)",placeholder:"group_abc123, group_def456",initialValue:h.length>0?h.join(", "):void 0,validate:b=>String(b??"").trim()?void 0:"Enter at least one group ID"});n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,groupAllowFrom:Fe(String(w))}}};}if(u!=="disabled"&&await t.confirm({message:"Restrict which users can trigger the bot in groups? (sender allowlist)",initialValue:true})){let w=n.channels?.seatalk?.groupSenderAllowFrom??[],b=await t.text({message:"Sender allowlist (emails or employee_codes, comma-separated)",placeholder:"alice@company.com, 12345678",initialValue:w.length>0?w.join(", "):void 0,validate:$=>String($??"").trim()?void 0:"Enter at least one user"});n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,groupSenderAllowFrom:Fe(String(b))}}};}let S=await t.select({message:"Processing indicator",options:[{value:"typing",label:"Typing \u2014 show typing status while processing (default)"},{value:"off",label:"Off \u2014 no processing indicator"}],initialValue:n.channels?.seatalk?.processingIndicator??"typing"});return n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,processingIndicator:String(S)}}},a&&(n=await ft({cfg:n,prompter:t})),{cfg:n}},dmPolicy:cn,disable:e=>({...e,channels:{...e.channels,seatalk:{...e.channels?.seatalk,enabled:false}}})};var Gn={id:"seatalk",label:"SeaTalk",selectionLabel:"SeaTalk (plugin)",blurb:"SeaTalk internal messaging integration.",docsPath:"/channels/seatalk",aliases:[],order:70,quickstartAllowFrom:true},qe={id:"seatalk",meta:Gn,pairing:{idLabel:"employeeCode",normalizeAllowEntry:createPairingPrefixStripper(/^(seatalk|st):/i),notifyApproval:async({cfg:e,id:t})=>{let a=Pe(e),n=A({cfg:e,accountId:a}),o=F(n);o&&await H(o,t,PAIRING_APPROVED_MESSAGE,1);}},capabilities:{chatTypes:["direct","group"],polls:false,threads:true,media:true,reactions:false,edit:false,reply:false},reload:{configPrefixes:["channels.seatalk"]},configSchema:buildChannelConfigSchema(Ze),config:{listAccountIds:e=>ye(e),resolveAccount:(e,t)=>A({cfg:e,accountId:t}),defaultAccountId:e=>Pe(e),setAccountEnabled:({cfg:e,accountId:t,enabled:a})=>{if(t===DEFAULT_ACCOUNT_ID)return {...e,channels:{...e.channels,seatalk:{...e.channels?.seatalk,enabled:a}}};let o=e.channels?.seatalk;return {...e,channels:{...e.channels,seatalk:{...o,accounts:{...o?.accounts,[t]:{...o?.accounts?.[t],enabled:a}}}}}},deleteAccount:({cfg:e,accountId:t})=>{if(t===DEFAULT_ACCOUNT_ID){let r={...e},s={...e.channels};s.seatalk=void 0;let l=Object.values(s).some(c=>c!==void 0);return r.channels=l?s:void 0,r}let n=e.channels?.seatalk,o={...n?.accounts};return delete o[t],{...e,channels:{...e.channels,seatalk:{...n,accounts:Object.keys(o).length>0?o:void 0}}}},isConfigured:e=>e.configured,describeAccount:e=>({accountId:e.accountId,enabled:e.enabled,configured:e.configured,appId:e.appId,mode:e.mode,...e.mode==="relay"?{relayUrl:e.relayUrl}:{webhookPort:e.webhookPort}}),resolveAllowFrom:({cfg:e,accountId:t})=>(A({cfg:e,accountId:t}).config?.allowFrom??[]).map(n=>String(n)),formatAllowFrom:({allowFrom:e})=>e.map(t=>String(t).trim()).filter(Boolean)},security:{collectWarnings:({cfg:e,accountId:t})=>{let a=A({cfg:e,accountId:t});return (a.config?.dmPolicy??"allowlist")!=="open"?[]:[`- SeaTalk[${a.accountId}]: dmPolicy="open" allows any subscriber to message the bot. Set channels.seatalk.dmPolicy to "allowlist" or "pairing" to restrict senders.`]}},setup:{resolveAccountId:()=>DEFAULT_ACCOUNT_ID,applyAccountConfig:({cfg:e,accountId:t})=>{if(!t||t===DEFAULT_ACCOUNT_ID)return {...e,channels:{...e.channels,seatalk:{...e.channels?.seatalk,enabled:true}}};let n=e.channels?.seatalk;return {...e,channels:{...e.channels,seatalk:{...n,accounts:{...n?.accounts,[t]:{...n?.accounts?.[t],enabled:true}}}}}}},setupWizard:mt,messaging:{normalizeTarget:e=>st(e)??void 0,targetResolver:{looksLikeId:it,hint:"<employee_code> or <email>"}},resolver:{resolveTargets:async({cfg:e,accountId:t,inputs:a})=>{let n=a.filter(c=>X(c));if(n.length===0)return a.map(c=>({input:c,resolved:true,id:c}));let o=c=>a.map(i=>{let g=X(i);return {input:i,resolved:!g,id:g?void 0:i,note:g?c:void 0}}),r=A({cfg:e,accountId:t}),s=F(r);if(!s)return o("SeaTalk client not available");let l=new Map;try{let c=await s.getEmployeeCodeByEmail(n);for(let i of c)i.employeeCode&&i.status===2&&l.set(i.email.toLowerCase(),i.employeeCode);}catch{return o("Failed to resolve email")}return a.map(c=>{if(!X(c))return {input:c,resolved:true,id:c};let i=l.get(c.toLowerCase());return i?{input:c,resolved:true,id:i,name:c}:{input:c,resolved:false,note:"No active employee found for this email"}})}},outbound:ut,status:{defaultRuntime:{accountId:DEFAULT_ACCOUNT_ID,running:false,lastStartAt:null,lastStopAt:null,lastError:null,port:null},buildChannelSummary:({snapshot:e})=>({configured:e.configured??false,running:e.running??false,lastStartAt:e.lastStartAt??null,lastStopAt:e.lastStopAt??null,lastError:e.lastError??null,port:e.port??null,probe:e.probe,lastProbeAt:e.lastProbeAt??null}),probeAccount:({account:e})=>ue(e),buildAccountSnapshot:({account:e,runtime:t,probe:a})=>({accountId:e.accountId,enabled:e.enabled,configured:e.configured,appId:e.appId,mode:e.mode,...e.mode==="relay"?{relayUrl:e.relayUrl}:{webhookPort:e.webhookPort},running:t?.running??false,lastStartAt:t?.lastStartAt??null,lastStopAt:t?.lastStopAt??null,lastError:t?.lastError??null,port:t?.port??null,probe:a})},gateway:{startAccount:async e=>{let t=A({cfg:e.cfg,accountId:e.accountId});if(t.mode==="relay"){if(!t.relayUrl)throw new Error(`SeaTalk account "${e.accountId}" mode=relay but relayUrl is not configured`);e.setStatus({accountId:e.accountId,mode:"relay"}),e.log?.info(`starting seatalk[${e.accountId}] (relay client \u2192 ${t.relayUrl})`);let{connectSeaTalkRelay:o}=await Promise.resolve().then(()=>(Mt(),Pt));return o({config:e.cfg,runtime:e.runtime,abortSignal:e.abortSignal,accountId:e.accountId,relayUrl:t.relayUrl})}e.setStatus({accountId:e.accountId,port:t.webhookPort}),e.log?.info(`starting seatalk[${e.accountId}] (webhook on port ${t.webhookPort})`);let{monitorSeaTalkProvider:n}=await Promise.resolve().then(()=>(He(),Dt));return n({config:e.cfg,runtime:e.runtime,abortSignal:e.abortSignal,accountId:e.accountId})}}};ce();N();K();var Ft="Pagination cursor. Omit for the first request to get the latest messages. To fetch older messages, pass the next_cursor value from the previous response.",Bt=Type.Union([Type.Object({action:Type.Literal("group_history",{description:"Get group chat message history (requires 'Get Chat History' app permission). Messages are returned in chronological order (oldest to newest). The first page (no cursor) contains the most recent messages. Use next_cursor from the response to fetch older pages."}),group_id:Type.String({description:"Group chat ID"}),page_size:Type.Optional(Type.Number({description:"Page size (1-100, default 50)"})),cursor:Type.Optional(Type.String({description:Ft}))}),Type.Object({action:Type.Literal("group_info",{description:"Get group chat details"}),group_id:Type.String({description:"Group chat ID"})}),Type.Object({action:Type.Literal("group_list",{description:"List groups the bot has joined"}),page_size:Type.Optional(Type.Number({description:"Page size (1-100, default 50)"})),cursor:Type.Optional(Type.String({description:"Pagination cursor. Omit for the first request. To fetch more groups, pass the next_cursor value from the previous response."}))}),Type.Object({action:Type.Literal("thread_history",{description:"Get thread messages in chronological order (oldest to newest). The first page (no cursor) contains the most recent replies. Use next_cursor to fetch older replies."}),thread_id:Type.String({description:"Thread ID"}),group_id:Type.Optional(Type.String({description:"Group chat ID (provide for group thread, omit for DM thread)"})),employee_code:Type.Optional(Type.String({description:"Employee code (required for DM thread when group_id is omitted)"})),page_size:Type.Optional(Type.Number({description:"Page size (1-100, default 50)"})),cursor:Type.Optional(Type.String({description:Ft}))}),Type.Object({action:Type.Literal("get_message",{description:"Get a message by its ID. Can resolve any message_id or quoted_message_id."}),message_id:Type.String({description:"The message ID to retrieve"})})]);function D(e){return {content:[{type:"text",text:JSON.stringify(e,null,2)}],details:e}}function jn(e){return {groupInfo:e?.groupInfo??true,groupHistory:e?.groupHistory??true,groupList:e?.groupList??true,threadHistory:e?.threadHistory??true,getMessage:e?.getMessage??true}}async function Lt(e,t,a){for(let n of t){if(!n||typeof n!="object")continue;let o=n.quoted_message_id;if(!(!o||typeof o!="string"))try{let r=await e.getMessageByMessageId(o);n.quoted_message=r;}catch(r){a?.(`seatalk tool: failed to resolve quoted message ${o}: ${String(r)}`),n.quoted_message=null;}}}function Ut(e,t){let a=e[t];if(Array.isArray(a)){let n=a.toReversed();return e[t]=n,n}return []}function Gt(e){if(!e.config){e.logger.debug?.("seatalk tool: No config available, skipping");return}let t=ne(e.config);if(t.length===0){e.logger.debug?.("seatalk tool: No enabled SeaTalk accounts, skipping");return}let a=t[0],n=jn(a.tools);if(!(n.groupInfo||n.groupHistory||n.groupList||n.threadHistory||n.getMessage)){e.logger.debug?.("seatalk tool: All actions disabled, skipping");return}let r=s=>e.logger.warn?.(s);e.registerTool(s=>{let l=s.agentAccountId,c=()=>{if(l){let i=A({cfg:e.config,accountId:l});if(i.configured)return F(i)}return F(a)};return {name:"seatalk",label:"SeaTalk",description:"SeaTalk operations. Actions: group_history (group chat messages, chronological order), group_info (group details), group_list (joined groups), thread_history (thread messages, chronological order), get_message (retrieve a single message by ID). History and thread results include resolved quoted_message for messages that quote another message.",parameters:Bt,async execute(i,g){let d=g;try{let p=c();if(!p)return D({error:`SeaTalk client not available${l?` for account ${l}`:""}`});switch(d.action){case "group_history":{if(!n.groupHistory)return D({error:"groupHistory is disabled in config"});let f=await p.getGroupChatHistory(d.group_id,{pageSize:d.page_size,cursor:d.cursor}),u=Ut(f,"group_chat_messages");return await Lt(p,u,r),D(f)}case "group_info":return n.groupInfo?D(await p.getGroupChatInfo(d.group_id)):D({error:"groupInfo is disabled in config"});case "group_list":return n.groupList?D(await p.getJoinedGroupChats({pageSize:d.page_size,cursor:d.cursor})):D({error:"groupList is disabled in config"});case "thread_history":{if(!n.threadHistory)return D({error:"threadHistory is disabled in config"});let f;if(d.group_id)f=await p.getGroupThread(d.group_id,d.thread_id,{pageSize:d.page_size,cursor:d.cursor});else {if(!d.employee_code)return D({error:"employee_code is required for DM thread (when group_id is absent)"});f=await p.getDmThread(d.employee_code,d.thread_id,{pageSize:d.page_size,cursor:d.cursor});}let u=Ut(f,"thread_messages");return await Lt(p,u,r),D(f)}case "get_message":return n.getMessage?D(await p.getMessageByMessageId(d.message_id)):D({error:"getMessage is disabled in config"});default:return D({error:`Unknown action: ${String(d.action)}`})}}catch(p){return D({error:p instanceof Error?p.message:String(p)})}}}},{name:"seatalk"}),e.logger.info?.("seatalk tool: Registered");}oe();He();var so=defineChannelPluginEntry({id:"openclaw-seatalk",name:"SeaTalk",description:"SeaTalk channel plugin",plugin:qe,setRuntime:et,registerFull:e=>Gt(e)});
20
- export{so as default,Ot as monitorSeaTalkProvider,ue as probeSeaTalk,qe as seatalkPlugin,ot as sendFileMessage,at as sendImageMessage,H as sendTextMessage};
16
+ `,idleFlushMs:Dt}):null,pe=v("outbound"),J=I.channel.reply.createReplyDispatcherWithTyping({humanDelay:I.channel.reply.resolveHumanDelayConfig(o,P.agentId),deliver:async E=>{let R=resolveSendableOutboundReplyParts(E);if(!(!R.hasText&&!R.hasMedia)){if(R.hasText)if(pe.info("group inline deliver",{accountId:a,groupId:s,threadId:ae,kind:"text"}),V)V.append(R.trimmedText);else {let se=Se(R.trimmedText,_e);for(let Re of se)await ge(Re);}R.hasMedia&&(pe.info("group inline deliver",{accountId:a,groupId:s,threadId:ae,kind:"media",count:R.mediaUrls.length}),V&&await V.flush(),await je({mediaUrls:R.mediaUrls,client:n,to:s,threadId:ae,isGroup:!0}));}},onError:E=>{pe.error("group reply delivery failed",{accountId:a,groupId:s,err:String(E)});}}),we={agentId:P.agentId,...J.replyOptions};r.info("group dispatching to agent",{accountId:a,groupId:s,sessionKey:w.sessionKey});try{let{queuedFinal:E,counts:R}=await I.channel.reply.dispatchReplyFromConfig({ctx:Me,cfg:o,dispatcher:J.dispatcher,replyOptions:we});r.info("group dispatch complete",{accountId:a,groupId:s,queuedFinal:E,counts:R});}finally{J.markDispatchIdle(),V&&(await J.dispatcher.waitForIdle(),await V.flush());}}catch(I){r.error("group dispatch failed",{accountId:a,groupId:s,err:String(I)});}}var On,$n,Dn,te,Et,$t,Bn,_e,Dt,Ie,ze=B(()=>{It();N();xt();X();Ce();At();fe();ce();On=1800*1e3,$n=1e3,Dn=300*1e3,te=new Map,Et=Date.now();$t=1500,Bn=5e3,_e=4e3,Dt=1e3,Ie=new Map;});var Gt={};Ve(Gt,{connectSeaTalkRelay:()=>Vn});function Wn(e,t){return new Promise(o=>{let n=()=>{clearTimeout(a),o();},a=setTimeout(()=>{t?.removeEventListener("abort",n),o();},e);t?.addEventListener("abort",n,{once:true});})}async function Ut(e){let{cfg:t,account:o,relayUrl:n,runtime:a,abortSignal:r}=e,{accountId:i}=o,s=v("relay"),l=z(o.config)?.signingSecret;if(!o.appId||!o.appSecret||!l)throw new Error(`SeaTalk account "${i}" missing credentials for relay mode`);let d=F(o);if(!d)throw new Error(`SeaTalk client not available for account "${i}"`);let u=Ft;for(;!r?.aborted;){try{await new Promise((c,f)=>{if(r?.aborted){c();return}s.info("connecting",{accountId:i,relayUrl:n});let g=new zn(n),m,y=()=>{m&&(clearTimeout(m),m=void 0);},h=()=>{y(),m=setTimeout(()=>{s.error("relay silent, terminating",{accountId:i,timeoutMs:Lt}),g.terminate();},Lt);},T=()=>{y(),g.close(),c();};r?.addEventListener("abort",T,{once:!0}),g.on("upgrade",_=>{_.socket.setKeepAlive(!0,6e4);}),g.on("open",()=>{h(),s.info("connected, authenticating",{accountId:i}),g.send(JSON.stringify({type:"auth",appId:o.appId,appSecret:o.appSecret,signingSecret:l}));});let C=!1;g.on("message",_=>{h();let S;try{S=JSON.parse(String(_));}catch{s.warn("invalid relay json",{accountId:i});return}if(!C){S.type==="auth_ok"?(C=!0,u=Ft,s.info("authenticated",{accountId:i})):S.type==="auth_fail"&&(s.error("auth failed",{accountId:i,err:S.error}),g.close(),f(new Error(`Relay auth failed: ${S.error}`)));return}switch(S.type){case "event":S.event&&d&&Pe({cfg:t,event:S.event,client:d,runtime:a,accountId:i});break;case "ping":g.send(JSON.stringify({type:"pong"}));break;case "replaced":s.warn("connection replaced",{accountId:i}),g.close(),c();return;default:s.warn("unknown relay message",{accountId:i,type:S.type});}}),g.on("close",(_,S)=>{y(),r?.removeEventListener("abort",T),C&&s.info("disconnected",{accountId:i,code:_,reason:String(S)}),c();}),g.on("error",_=>{y(),r?.removeEventListener("abort",T),s.error("connection error",{accountId:i,err:String(_)}),c();});});}catch(c){let f=String(c);if(f.includes("Relay auth failed"))throw c;s.error("relay error",{accountId:i,err:f});}if(r?.aborted)break;s.info("reconnecting",{accountId:i,backoffMs:u}),await Wn(u,r),u=Math.min(u*Kn,Nn);}}async function Vn(e){let t=e.config;if(!t)throw new Error("Config is required for SeaTalk relay client");let o=v("relay");if(e.accountId){let a=x({cfg:t,accountId:e.accountId});if(!a.enabled||!a.configured)throw new Error(`SeaTalk account "${e.accountId}" not configured or disabled`);return Ut({cfg:t,account:a,relayUrl:e.relayUrl,runtime:e.runtime,abortSignal:e.abortSignal})}let n=ie(t);if(n.length===0)throw new Error("No enabled SeaTalk accounts configured");o.info("connecting accounts",{count:n.length,accountIds:n.map(a=>a.accountId)}),await Promise.all(n.map(a=>Ut({cfg:t,account:a,relayUrl:e.relayUrl,runtime:e.runtime,abortSignal:e.abortSignal})));}var Ft,Nn,Kn,Lt,jt=B(()=>{N();ze();Y();X();Ft=1e3,Nn=3e4,Kn=2,Lt=75e3;});var Nt={};Ve(Nt,{monitorSeaTalkProvider:()=>zt});function Jn(e,t,o){let n=Buffer.from(t,"latin1"),a=Ae.createHash("sha256").update(Buffer.concat([e,n])).digest("hex");try{return Ae.timingSafeEqual(Buffer.from(a,"hex"),Buffer.from(o,"hex"))}catch{return false}}function Yn(e){return new Promise((t,o)=>{let n=0,a=[];e.on("data",r=>{if(n+=r.length,n>Xn){e.destroy(new xe);return}a.push(r);}),e.on("end",()=>t(Buffer.concat(a))),e.on("error",o);})}async function Ht(e){let{cfg:t,account:o,runtime:n,abortSignal:a}=e,{accountId:r}=o,i=v("webhook"),s=o.webhookPort,l=o.webhookPath,d=z(o.config)?.signingSecret;if(!d)throw new Error(`SeaTalk account "${r}" missing signingSecret`);let u=F(o);if(!u)throw new Error(`SeaTalk client not available for account "${r}"`);i.info("starting webhook",{accountId:r,port:s,path:l});let c=qt.createServer();return c.on("request",async(f,g)=>{let m=new URL(f.url??"/",`http://localhost:${s}`).pathname;if(f.method!=="POST"||m!==l){g.writeHead(404),g.end("Not Found");return}try{let y=await Yn(f),h=f.headers.signature;if(!h||!Jn(y,d,h)){i.warn("signature verification failed",{accountId:r}),g.writeHead(403),g.end("Forbidden");return}let T=JSON.parse(y.toString("utf-8"));if(T.event_type==="event_verification"){let C=T.event?.seatalk_challenge;g.writeHead(200,{"Content-Type":"application/json"}),g.end(JSON.stringify({seatalk_challenge:C})),i.info("url verification responded",{accountId:r});return}g.writeHead(200),g.end("OK"),Pe({cfg:t,event:T,client:u,runtime:n,accountId:r});}catch(y){i.error("request processing error",{accountId:r,err:String(y)}),g.headersSent||(y instanceof xe?(g.writeHead(413),g.end("Payload Too Large")):(g.writeHead(500),g.end("Internal Server Error")));}}),new Promise((f,g)=>{let m=()=>{c.close();},y=()=>{i.info("abort received, stopping webhook",{accountId:r}),m(),f();};if(a?.aborted){m(),f();return}a?.addEventListener("abort",y,{once:true}),c.listen(s,()=>{i.info("webhook listening",{accountId:r,port:s});}),c.on("error",h=>{i.error("webhook server error",{accountId:r,err:String(h)}),a?.removeEventListener("abort",y),g(h);});})}async function zt(e={}){let t=e.config;if(!t)throw new Error("Config is required for SeaTalk monitor");let o=v("webhook");if(e.accountId){let a=x({cfg:t,accountId:e.accountId});if(!a.enabled||!a.configured)throw new Error(`SeaTalk account "${e.accountId}" not configured or disabled`);return Ht({cfg:t,account:a,runtime:e.runtime,abortSignal:e.abortSignal})}let n=ie(t);if(n.length===0)throw new Error("No enabled SeaTalk accounts configured");o.info("starting accounts",{count:n.length,accountIds:n.map(a=>a.accountId)}),await Promise.all(n.map(a=>Ht({cfg:t,account:a,runtime:e.runtime,abortSignal:e.abortSignal})));}var Xn,xe,Ne=B(()=>{N();ze();Y();X();Xn=1024*1024,xe=class extends Error{constructor(){super("Request body too large"),this.name="PayloadTooLargeError";}};});N();Y();var et=z$1.enum(["open","allowlist","pairing"]),tt=z$1.enum(["webhook","relay"]),nt=z$1.enum(["disabled","allowlist","open"]),ot=z$1.enum(["typing","off"]),sn=z$1.object({groupInfo:z$1.boolean().optional().default(true),groupHistory:z$1.boolean().optional().default(true),groupList:z$1.boolean().optional().default(true),threadHistory:z$1.boolean().optional().default(true),getMessage:z$1.boolean().optional().default(true)}).strict(),ln=z$1.object({enabled:z$1.boolean().optional(),appId:z$1.string().optional(),appSecret:z$1.string().optional(),signingSecret:z$1.string().optional(),mode:tt.optional(),relayUrl:z$1.string().optional(),webhookPort:z$1.number().int().positive().optional(),webhookPath:z$1.string().optional(),dmPolicy:et.optional(),allowFrom:z$1.array(z$1.string()).optional(),dmThreadSession:z$1.boolean().optional(),groupPolicy:nt.optional(),groupAllowFrom:z$1.array(z$1.string()).optional(),groupSenderAllowFrom:z$1.array(z$1.string()).optional(),groupThreadSession:z$1.boolean().optional(),threadInheritParent:z$1.boolean().optional(),processingIndicator:ot.optional(),mediaAllowHosts:z$1.array(z$1.string()).optional(),outboundCoalescing:z$1.boolean().optional()}).strict(),at=z$1.object({enabled:z$1.boolean().optional(),appId:z$1.string().optional(),appSecret:z$1.string().optional(),signingSecret:z$1.string().optional(),mode:tt.optional().default("webhook"),relayUrl:z$1.string().optional(),webhookPort:z$1.number().int().positive().optional().default(8080),webhookPath:z$1.string().optional().default("/callback"),dmPolicy:et.optional().default("allowlist"),allowFrom:z$1.array(z$1.string()).optional(),dmThreadSession:z$1.boolean().optional().default(true),groupPolicy:nt.optional().default("disabled"),groupAllowFrom:z$1.array(z$1.string()).optional(),groupSenderAllowFrom:z$1.array(z$1.string()).optional(),groupThreadSession:z$1.boolean().optional().default(true),threadInheritParent:z$1.boolean().optional().default(true),processingIndicator:ot.optional().default("typing"),mediaAllowHosts:z$1.array(z$1.string()).optional(),outboundCoalescing:z$1.boolean().optional().default(true),tools:sn.optional(),accounts:z$1.record(z$1.string(),ln.optional()).optional()}).strict().superRefine((e,t)=>{e.dmPolicy==="open"&&((e.allowFrom??[]).some(a=>a.trim()==="*")||t.addIssue({code:z$1.ZodIssueCode.custom,path:["allowFrom"],message:'channels.seatalk.dmPolicy="open" requires channels.seatalk.allowFrom to include "*"'}));});N();Y();fe();ce();var dt="group:";function ut(e){let t=e.trim();return t||null}function K(e){return e.startsWith(dt)}function W(e){return e.slice(dt.length)}function ee(e){return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e.trim())}function gt(e){let t=e.trim();return t?ee(t)?true:K(t)?W(t).length>0:/^[a-zA-Z0-9_-]+$/.test(t):false}function pt(e,t){let o=x({cfg:e,accountId:t}),n=F(o);if(!n)throw new Error(`SeaTalk client not available for account ${o.accountId}`);return n}async function ft(e,t){if(!ee(t))return t;let n=(await e.getEmployeeCodeByEmail([t])).find(a=>a.employeeCode&&a.status===2);if(n?.employeeCode)return n.employeeCode;throw new Error(`No active SeaTalk employee found for email: ${t}`)}function mt(e){if(e!=null)return String(e)}var ht={deliveryMode:"direct",chunker:(e,t)=>Q().channel.text.chunkMarkdownText(e,t),chunkerMode:"markdown",textChunkLimit:4e3,sendText:async({cfg:e,to:t,text:o,accountId:n,threadId:a})=>{let r=pt(e,n??void 0),i=mt(a);if(K(t)){let l=W(t);return await le(r,l,o,1,i),{channel:"seatalk",messageId:"",chatId:t}}let s=await ft(r,t);return await H(r,s,o,1,i),{channel:"seatalk",messageId:"",chatId:s}},sendMedia:async({cfg:e,to:t,text:o,mediaUrl:n,accountId:a,threadId:r})=>{let i=pt(e,a??void 0),s=mt(r),l=K(t),d=l?W(t):await ft(i,t);if(o?.trim()&&(l?await le(i,d,o,1,s):await H(i,d,o,1,s)),n)try{await ve({client:i,to:d,mediaUrl:n,threadId:s,isGroup:l});}catch(u){let c=`[Media send failed: ${u instanceof Error?u.message:String(u)}]`;l?await le(i,d,c,2,s):await H(i,d,c,2,s);}return {channel:"seatalk",messageId:"",chatId:l?t:d}}};Y();async function he(e){if(!e?.appId||!e?.appSecret)return {ok:false,error:"missing credentials (appId, appSecret)"};try{let t=De(e.appId,e.appSecret),o=Date.now();await t.getAccessToken();let n=Date.now()-o;return {ok:!0,appId:e.appId,latencyMs:n}}catch(t){return {ok:false,appId:e.appId,error:t instanceof Error?t.message:String(t)}}}ce();function yt(e){let t=stripChannelTargetPrefix(e.target,"seatalk");if(!t)return null;let o=K(t),n=o?W(t):t;if(!n)return null;let a=buildChannelOutboundSessionRoute({cfg:e.cfg,agentId:e.agentId,channel:"seatalk",accountId:e.accountId,peer:{kind:o?"group":"direct",id:n},chatType:o?"group":"direct",from:o?`seatalk:group:${n}`:`seatalk:${n}`,to:o?`group:${n}`:n});return buildThreadAwareOutboundSessionRoute({route:a,replyToId:e.replyToId,threadId:e.threadId,currentSessionKey:e.currentSessionKey,canRecoverCurrentThread:({route:r})=>r.chatType!=="direct"||(e.cfg.session?.dmScope??"main")!=="main"})}N();var St="seatalk";function Ue(e){return e.split(/[\n,;]+/g).map(t=>t.trim()).filter(Boolean)}async function wt(e){let{cfg:t,prompter:o}=e,n=t.channels?.seatalk?.allowFrom??[];for(await o.note(["Allowlist SeaTalk DMs by email or employee_code.","Examples:","- alice@company.com","- 12345678"].join(`
17
+ `),"SeaTalk allowlist");;){let a=await o.text({message:"SeaTalk allowFrom (emails or employee_codes)",placeholder:"alice@company.com, 12345678",initialValue:n[0]?String(n[0]):void 0,validate:s=>String(s??"").trim()?void 0:"Required"}),r=Ue(String(a));if(r.length===0){await o.note("Enter at least one user.","SeaTalk allowlist");continue}let i=mergeAllowFromEntries(n.map(s=>String(s)),r);return {...t,channels:{...t.channels,seatalk:{...t.channels?.seatalk,allowFrom:i}}}}}var vn=createTopLevelChannelDmPolicy({label:"SeaTalk",channel:St,policyKey:"channels.seatalk.dmPolicy",allowFromKey:"channels.seatalk.allowFrom",getCurrent:e=>e.channels?.seatalk?.dmPolicy??"allowlist",promptAllowFrom:async({cfg:e,prompter:t})=>wt({cfg:e,prompter:t})});async function kt(e){let t=String(await e.text({message:"Enter SeaTalk App ID",validate:a=>a?.trim()?void 0:"Required"})).trim(),o=String(await e.text({message:"Enter SeaTalk App Secret",validate:a=>a?.trim()?void 0:"Required"})).trim(),n=String(await e.text({message:"Enter SeaTalk Signing Secret",validate:a=>a?.trim()?void 0:"Required"})).trim();return {appId:t,appSecret:o,signingSecret:n}}async function _n(e){await e.note(["1) Go to SeaTalk Open Platform (open.seatalk.io)","2) Create a Bot App","3) Get App ID and App Secret from Basic Info & Credentials","4) Get Signing Secret from Event Callback settings","5) Enable Bot capability and set status to Online",'6) Enable "Send Message to Bot User" permission'].join(`
18
+ `),"SeaTalk credentials");}var Tt={channel:St,status:createStandardChannelSetupStatus({channelLabel:"SeaTalk",configuredLabel:"configured",unconfiguredLabel:"needs app credentials",configuredHint:"configured",unconfiguredHint:"needs app creds",configuredScore:2,unconfiguredScore:0,resolveConfigured:({cfg:e})=>{let t=e.channels?.seatalk;return !!z(t)}}),credentials:[],finalize:async({cfg:e,prompter:t,forceAllowFrom:o})=>{let n=e,a=n.channels?.seatalk,r=z(a),i=!!(a?.appId?.trim()&&a?.appSecret?.trim()&&a?.signingSecret?.trim()),s=null,l=null,d=null;if(r||await _n(t),i?await t.confirm({message:"SeaTalk credentials already configured. Keep them?",initialValue:true})||({appId:s,appSecret:l,signingSecret:d}=await kt(t)):{appId:s,appSecret:l,signingSecret:d}=await kt(t),s&&l&&d){n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,enabled:true,appId:s,appSecret:l,signingSecret:d,dmPolicy:a?.dmPolicy??"allowlist"}}};try{let h=await he({appId:s,appSecret:l});h.ok?await t.note(`Connected successfully (latency: ${h.latencyMs}ms)`,"SeaTalk connection test"):await t.note(`Connection failed: ${h.error??"unknown error"}`,"SeaTalk connection test");}catch(h){await t.note(`Connection test failed: ${String(h)}`,"SeaTalk connection test");}await t.note(["Important reminders:",'- Bot App must be set to "Online" status in SeaTalk Open Platform','- "Send Message to Bot User" permission must be enabled',"- Configure the callback URL in Event Callback settings"].join(`
19
+ `),"SeaTalk setup");}let u=n.channels?.seatalk?.mode??"webhook",c=await t.select({message:"Gateway mode",options:[{value:"webhook",label:"Webhook \u2014 receive event callbacks directly (default)"},{value:"relay",label:"Relay \u2014 connect to a relay service as client"}],initialValue:u}),f=String(c);if(n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,mode:f}}},f==="relay"){let h=n.channels?.seatalk?.relayUrl??"",T=await t.text({message:"Relay WebSocket URL",placeholder:"wss://relay.example.com/ws",initialValue:h||void 0,validate:_=>{let S=String(_??"").trim();if(!S)return "Required";if(!S.startsWith("ws://")&&!S.startsWith("wss://"))return "Must be a ws:// or wss:// URL"}}),C=String(T).trim();C.startsWith("ws://")&&await t.note("ws:// transmits credentials (appSecret, signingSecret) unencrypted. Consider using wss:// for production.","Security warning"),n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,relayUrl:C}}};}else {let h=n.channels?.seatalk?.webhookPort??8080,T=await t.text({message:"Webhook port",initialValue:String(h),validate:A=>{let L=Number(A);return L>0&&L<65536?void 0:"Enter a valid port number (1-65535)"}}),C=Number(T);C&&C!==h&&(n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,webhookPort:C}}});let _=n.channels?.seatalk?.webhookPath??"/callback",S=await t.text({message:"Webhook path",initialValue:_,validate:A=>{let L=String(A??"").trim();if(!L)return "Required";if(!L.startsWith("/"))return "Path must start with /"}}),q=String(S??_).trim();q&&q!==_&&(n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,webhookPath:q}}});}let g=await t.select({message:"Group chat policy",options:[{value:"disabled",label:"Disabled \u2014 ignore all group messages (default)"},{value:"allowlist",label:"Allowlist \u2014 respond only in specific groups"},{value:"open",label:"Open \u2014 respond in all groups the bot joins"}],initialValue:n.channels?.seatalk?.groupPolicy??"disabled"}),m=String(g);if(n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,groupPolicy:m}}},m==="allowlist"){let h=n.channels?.seatalk?.groupAllowFrom??[],T=await t.text({message:"Allowed group IDs (comma-separated)",placeholder:"group_abc123, group_def456",initialValue:h.length>0?h.join(", "):void 0,validate:C=>String(C??"").trim()?void 0:"Enter at least one group ID"});n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,groupAllowFrom:Ue(String(T))}}};}if(m!=="disabled"&&await t.confirm({message:"Restrict which users can trigger the bot in groups? (sender allowlist)",initialValue:true})){let T=n.channels?.seatalk?.groupSenderAllowFrom??[],C=await t.text({message:"Sender allowlist (emails or employee_codes, comma-separated)",placeholder:"alice@company.com, 12345678",initialValue:T.length>0?T.join(", "):void 0,validate:_=>String(_??"").trim()?void 0:"Enter at least one user"});n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,groupSenderAllowFrom:Ue(String(C))}}};}let y=await t.select({message:"Processing indicator",options:[{value:"typing",label:"Typing \u2014 show typing status while processing (default)"},{value:"off",label:"Off \u2014 no processing indicator"}],initialValue:n.channels?.seatalk?.processingIndicator??"typing"});return n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,processingIndicator:String(y)}}},o&&(n=await wt({cfg:n,prompter:t})),{cfg:n}},dmPolicy:vn,disable:e=>({...e,channels:{...e.channels,seatalk:{...e.channels?.seatalk,enabled:false}}})};function Ct(e){let t=e.context.To,o=t?.startsWith("group:")?t.slice(6):normalizeOptionalString(t),n=typeof e.context.MessageThreadId=="number"?String(e.context.MessageThreadId):normalizeOptionalString(e.context.MessageThreadId);return {currentChannelId:o,currentThreadTs:n,replyToMode:"all",hasRepliedRef:e.hasRepliedRef}}function vt(e){let t=e.toolContext;if(!(!t?.currentThreadTs||!t.currentChannelId||(K(e.to)?W(e.to):e.to)!==t.currentChannelId))return t.currentThreadTs}var to={id:"seatalk",label:"SeaTalk",selectionLabel:"SeaTalk (plugin)",blurb:"SeaTalk internal messaging integration.",docsPath:"/channels/seatalk",aliases:[],order:70,quickstartAllowFrom:true},Ke={id:"seatalk",meta:to,pairing:{idLabel:"employeeCode",normalizeAllowEntry:createPairingPrefixStripper(/^(seatalk|st):/i),notifyApproval:async({cfg:e,id:t})=>{let o=Oe(e),n=x({cfg:e,accountId:o}),a=F(n);a&&await H(a,t,PAIRING_APPROVED_MESSAGE,1);}},capabilities:{chatTypes:["direct","group"],polls:false,threads:true,media:true,reactions:false,edit:false,reply:false},reload:{configPrefixes:["channels.seatalk"]},configSchema:buildChannelConfigSchema(at),config:{listAccountIds:e=>be(e),resolveAccount:(e,t)=>x({cfg:e,accountId:t}),defaultAccountId:e=>Oe(e),setAccountEnabled:({cfg:e,accountId:t,enabled:o})=>{if(t===DEFAULT_ACCOUNT_ID)return {...e,channels:{...e.channels,seatalk:{...e.channels?.seatalk,enabled:o}}};let a=e.channels?.seatalk;return {...e,channels:{...e.channels,seatalk:{...a,accounts:{...a?.accounts,[t]:{...a?.accounts?.[t],enabled:o}}}}}},deleteAccount:({cfg:e,accountId:t})=>{if(t===DEFAULT_ACCOUNT_ID){let r={...e},i={...e.channels};i.seatalk=void 0;let s=Object.values(i).some(l=>l!==void 0);return r.channels=s?i:void 0,r}let n=e.channels?.seatalk,a={...n?.accounts};return delete a[t],{...e,channels:{...e.channels,seatalk:{...n,accounts:Object.keys(a).length>0?a:void 0}}}},isConfigured:e=>e.configured,describeAccount:e=>({accountId:e.accountId,enabled:e.enabled,configured:e.configured,appId:e.appId,mode:e.mode,...e.mode==="relay"?{relayUrl:e.relayUrl}:{webhookPort:e.webhookPort}}),resolveAllowFrom:({cfg:e,accountId:t})=>(x({cfg:e,accountId:t}).config?.allowFrom??[]).map(n=>String(n)),formatAllowFrom:({allowFrom:e})=>e.map(t=>String(t).trim()).filter(Boolean)},security:{collectWarnings:({cfg:e,accountId:t})=>{let o=x({cfg:e,accountId:t});return (o.config?.dmPolicy??"allowlist")!=="open"?[]:[`- SeaTalk[${o.accountId}]: dmPolicy="open" allows any subscriber to message the bot. Set channels.seatalk.dmPolicy to "allowlist" or "pairing" to restrict senders.`]}},setup:{resolveAccountId:()=>DEFAULT_ACCOUNT_ID,applyAccountConfig:({cfg:e,accountId:t})=>{if(!t||t===DEFAULT_ACCOUNT_ID)return {...e,channels:{...e.channels,seatalk:{...e.channels?.seatalk,enabled:true}}};let n=e.channels?.seatalk;return {...e,channels:{...e.channels,seatalk:{...n,accounts:{...n?.accounts,[t]:{...n?.accounts?.[t],enabled:true}}}}}}},setupWizard:Tt,messaging:{normalizeTarget:e=>ut(e)??void 0,resolveOutboundSessionRoute:e=>yt(e),targetResolver:{looksLikeId:gt,hint:"<employee_code> or <email>"}},threading:{buildToolContext:e=>Ct(e),resolveAutoThreadId:e=>vt(e)},resolver:{resolveTargets:async({cfg:e,accountId:t,inputs:o})=>{let n=o.filter(l=>ee(l));if(n.length===0)return o.map(l=>({input:l,resolved:true,id:l}));let a=l=>o.map(d=>{let u=ee(d);return {input:d,resolved:!u,id:u?void 0:d,note:u?l:void 0}}),r=x({cfg:e,accountId:t}),i=F(r);if(!i)return a("SeaTalk client not available");let s=new Map;try{let l=await i.getEmployeeCodeByEmail(n);for(let d of l)d.employeeCode&&d.status===2&&s.set(d.email.toLowerCase(),d.employeeCode);}catch{return a("Failed to resolve email")}return o.map(l=>{if(!ee(l))return {input:l,resolved:true,id:l};let d=s.get(l.toLowerCase());return d?{input:l,resolved:true,id:d,name:l}:{input:l,resolved:false,note:"No active employee found for this email"}})}},outbound:ht,status:{defaultRuntime:{accountId:DEFAULT_ACCOUNT_ID,running:false,lastStartAt:null,lastStopAt:null,lastError:null,port:null},buildChannelSummary:({snapshot:e})=>({configured:e.configured??false,running:e.running??false,lastStartAt:e.lastStartAt??null,lastStopAt:e.lastStopAt??null,lastError:e.lastError??null,port:e.port??null,probe:e.probe,lastProbeAt:e.lastProbeAt??null}),probeAccount:({account:e})=>he(e),buildAccountSnapshot:({account:e,runtime:t,probe:o})=>({accountId:e.accountId,enabled:e.enabled,configured:e.configured,appId:e.appId,mode:e.mode,...e.mode==="relay"?{relayUrl:e.relayUrl}:{webhookPort:e.webhookPort},running:t?.running??false,lastStartAt:t?.lastStartAt??null,lastStopAt:t?.lastStopAt??null,lastError:t?.lastError??null,port:t?.port??null,probe:o})},gateway:{startAccount:async e=>{let t=x({cfg:e.cfg,accountId:e.accountId});if(t.mode==="relay"){if(!t.relayUrl)throw new Error(`SeaTalk account "${e.accountId}" mode=relay but relayUrl is not configured`);e.setStatus({accountId:e.accountId,mode:"relay"}),e.log?.info(`starting seatalk[${e.accountId}] (relay client \u2192 ${t.relayUrl})`);let{connectSeaTalkRelay:a}=await Promise.resolve().then(()=>(jt(),Gt));return a({config:e.cfg,runtime:e.runtime,abortSignal:e.abortSignal,accountId:e.accountId,relayUrl:t.relayUrl})}e.setStatus({accountId:e.accountId,port:t.webhookPort}),e.log?.info(`starting seatalk[${e.accountId}] (webhook on port ${t.webhookPort})`);let{monitorSeaTalkProvider:n}=await Promise.resolve().then(()=>(Ne(),Nt));return n({config:e.cfg,runtime:e.runtime,abortSignal:e.abortSignal,accountId:e.accountId})}}};fe();N();Y();var Kt="Pagination cursor. Omit for the first request to get the latest messages. To fetch older messages, pass the next_cursor value from the previous response.",Wt=Type.Union([Type.Object({action:Type.Literal("group_history",{description:"Get group chat message history (requires 'Get Chat History' app permission). Messages are returned in chronological order (oldest to newest). The first page (no cursor) contains the most recent messages. Use next_cursor from the response to fetch older pages."}),group_id:Type.String({description:"Group chat ID"}),page_size:Type.Optional(Type.Number({description:"Page size (1-100, default 50)"})),cursor:Type.Optional(Type.String({description:Kt}))}),Type.Object({action:Type.Literal("group_info",{description:"Get group chat details"}),group_id:Type.String({description:"Group chat ID"})}),Type.Object({action:Type.Literal("group_list",{description:"List groups the bot has joined"}),page_size:Type.Optional(Type.Number({description:"Page size (1-100, default 50)"})),cursor:Type.Optional(Type.String({description:"Pagination cursor. Omit for the first request. To fetch more groups, pass the next_cursor value from the previous response."}))}),Type.Object({action:Type.Literal("thread_history",{description:"Get thread messages in chronological order (oldest to newest). The first page (no cursor) contains the most recent replies. Use next_cursor to fetch older replies."}),thread_id:Type.String({description:"Thread ID"}),group_id:Type.Optional(Type.String({description:"Group chat ID (provide for group thread, omit for DM thread)"})),employee_code:Type.Optional(Type.String({description:"Employee code (required for DM thread when group_id is omitted)"})),page_size:Type.Optional(Type.Number({description:"Page size (1-100, default 50)"})),cursor:Type.Optional(Type.String({description:Kt}))}),Type.Object({action:Type.Literal("get_message",{description:"Get a message by its ID. Can resolve any message_id or quoted_message_id."}),message_id:Type.String({description:"The message ID to retrieve"})})]);function $(e){return {content:[{type:"text",text:JSON.stringify(e,null,2)}],details:e}}function no(e){return {groupInfo:e?.groupInfo??true,groupHistory:e?.groupHistory??true,groupList:e?.groupList??true,threadHistory:e?.threadHistory??true,getMessage:e?.getMessage??true}}async function Vt(e,t,o){for(let n of t){if(!n||typeof n!="object")continue;let a=n.quoted_message_id;if(!(!a||typeof a!="string"))try{let r=await e.getMessageByMessageId(a);n.quoted_message=r;}catch(r){o?.(`seatalk tool: failed to resolve quoted message ${a}: ${String(r)}`),n.quoted_message=null;}}}function Jt(e,t){let o=e[t];if(Array.isArray(o)){let n=o.toReversed();return e[t]=n,n}return []}function Xt(e){if(!e.config){e.logger.debug?.("seatalk tool: No config available, skipping");return}let t=ie(e.config);if(t.length===0){e.logger.debug?.("seatalk tool: No enabled SeaTalk accounts, skipping");return}let o=t[0],n=no(o.tools);if(!(n.groupInfo||n.groupHistory||n.groupList||n.threadHistory||n.getMessage)){e.logger.debug?.("seatalk tool: All actions disabled, skipping");return}let r=i=>e.logger.warn?.(i);e.registerTool(i=>{let s=i.agentAccountId,l=()=>{if(s){let d=x({cfg:e.config,accountId:s});if(d.configured)return F(d)}return F(o)};return {name:"seatalk",label:"SeaTalk",description:"SeaTalk operations. Actions: group_history (group chat messages, chronological order), group_info (group details), group_list (joined groups), thread_history (thread messages, chronological order), get_message (retrieve a single message by ID). History and thread results include resolved quoted_message for messages that quote another message.",parameters:Wt,async execute(d,u){let c=u;try{let f=l();if(!f)return $({error:`SeaTalk client not available${s?` for account ${s}`:""}`});switch(c.action){case "group_history":{if(!n.groupHistory)return $({error:"groupHistory is disabled in config"});let g=await f.getGroupChatHistory(c.group_id,{pageSize:c.page_size,cursor:c.cursor}),m=Jt(g,"group_chat_messages");return await Vt(f,m,r),$(g)}case "group_info":return n.groupInfo?$(await f.getGroupChatInfo(c.group_id)):$({error:"groupInfo is disabled in config"});case "group_list":return n.groupList?$(await f.getJoinedGroupChats({pageSize:c.page_size,cursor:c.cursor})):$({error:"groupList is disabled in config"});case "thread_history":{if(!n.threadHistory)return $({error:"threadHistory is disabled in config"});let g;if(c.group_id)g=await f.getGroupThread(c.group_id,c.thread_id,{pageSize:c.page_size,cursor:c.cursor});else {if(!c.employee_code)return $({error:"employee_code is required for DM thread (when group_id is absent)"});g=await f.getDmThread(c.employee_code,c.thread_id,{pageSize:c.page_size,cursor:c.cursor});}let m=Jt(g,"thread_messages");return await Vt(f,m,r),$(g)}case "get_message":return n.getMessage?$(await f.getMessageByMessageId(c.message_id)):$({error:"getMessage is disabled in config"});default:return $({error:`Unknown action: ${String(c.action)}`})}}catch(f){return $({error:f instanceof Error?f.message:String(f)})}}}},{name:"seatalk"}),e.logger.info?.("seatalk tool: Registered");}ce();Ne();var Ua=defineChannelPluginEntry({id:"openclaw-seatalk",name:"SeaTalk",description:"SeaTalk channel plugin",plugin:Ke,setRuntime:rt,registerFull:e=>Xt(e)});
20
+ export{Ua as default,zt as monitorSeaTalkProvider,he as probeSeaTalk,Ke as seatalkPlugin,ct as sendFileMessage,lt as sendImageMessage,H as sendTextMessage};
@@ -1,20 +1,20 @@
1
- import {buildChannelConfigSchema,defineSetupPluginEntry,DEFAULT_ACCOUNT_ID,normalizeAccountId,PAIRING_APPROVED_MESSAGE}from'openclaw/plugin-sdk/core';import*as L from'fs';import*as Xe from'os';import*as W from'path';import {resolveSendableOutboundReplyParts,sendMediaWithLeadingCaption}from'openclaw/plugin-sdk/reply-payload';import {createPairingPrefixStripper,createChannelPairingController}from'openclaw/plugin-sdk/channel-pairing';import {resolveDmGroupAccessWithLists,DM_GROUP_ACCESS_REASON}from'openclaw/plugin-sdk/channel-policy';import wn from'ws';import*as _e from'crypto';import*as $t from'http';import {z as z$1}from'zod';import {createTopLevelChannelDmPolicy,createStandardChannelSetupStatus,mergeAllowFromEntries}from'openclaw/plugin-sdk/setup';var xt=Object.defineProperty;var D=(e,t)=>()=>(e&&(t=e(e=0)),t);var je=(e,t)=>{for(var a in t)xt(e,a,{get:t[a],enumerable:true});};function Ot(e){let t=e.channels?.seatalk?.accounts;return !t||typeof t!="object"?[]:Object.keys(t).filter(Boolean)}function pe(e){let t=Ot(e);return t.length===0?[DEFAULT_ACCOUNT_ID]:[...t].toSorted((a,n)=>a.localeCompare(n))}function Ee(e){let t=pe(e);return t.includes(DEFAULT_ACCOUNT_ID)?DEFAULT_ACCOUNT_ID:t[0]??DEFAULT_ACCOUNT_ID}function Ft(e,t){let a=e.channels?.seatalk?.accounts;if(!(!a||typeof a!="object"))return a[t]}function Dt(e,t){let a=e.channels?.seatalk,{accounts:n,...o}=a??{},r=Ft(e,t)??{};return {...o,...r}}function G(e){let t=e?.appId?.trim(),a=e?.appSecret?.trim(),n=e?.signingSecret?.trim();return !t||!a||!n?null:{appId:t,appSecret:a,signingSecret:n}}function A(e){let t=normalizeAccountId(e.accountId),n=e.cfg.channels?.seatalk?.enabled!==false,o=Dt(e.cfg,t),r=o.enabled!==false,s=n&&r,l=G(o);return {accountId:t,enabled:s,configured:!!l,appId:l?.appId,appSecret:l?.appSecret,mode:o.mode??"webhook",relayUrl:o.relayUrl,webhookPort:o.webhookPort??8080,webhookPath:o.webhookPath??"/callback",tools:o.tools,config:o}}function fe(e){return pe(e).map(t=>A({cfg:e,accountId:t})).filter(t=>t.enabled&&t.configured)}var q=D(()=>{});function Pe(e,t){let a=`${e}:${t}`,n=He.get(a);return n||(n=new Ae(e,t),He.set(a,n)),n}function U(e){return !e.appId||!e.appSecret?null:Pe(e.appId,e.appSecret)}var Ge,Ne,Ae,He,Z=D(()=>{Ge="https://openapi.seatalk.io",Ne=[1e4,6e4],Ae=class{appId;appSecret;tokenInfo=null;tokenPromise=null;constructor(t,a){this.appId=t,this.appSecret=a;}async getAccessToken(){if(this.tokenInfo){let t=Math.floor(Date.now()/1e3);if(this.tokenInfo.expireAt-t>600)return this.tokenInfo.token}return (await this.refreshToken()).token}async refreshToken(){if(this.tokenPromise)return this.tokenPromise;this.tokenPromise=this._fetchToken();try{let t=await this.tokenPromise;return this.tokenInfo=t,t}finally{this.tokenPromise=null;}}async _fetchToken(){let t=new AbortController,a=setTimeout(()=>t.abort(),1e4);try{let n=await fetch(`${Ge}/auth/app_access_token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({app_id:this.appId,app_secret:this.appSecret}),signal:t.signal});if(!n.ok){let r=n.headers.get("x-rid")??void 0;throw new Error(`SeaTalk token request failed: HTTP ${n.status} (x-rid: ${r})`)}let o=await n.json();if(o.code!==0)throw new Error(`SeaTalk token error: code=${o.code} message=${o.message??"unknown"}`);if(!o.app_access_token||!o.expire)throw new Error("SeaTalk token response missing token or expire");return {token:o.app_access_token,expireAt:o.expire}}finally{clearTimeout(a);}}async apiCall(t,a,n,o=true,r=0){let s=await this.getAccessToken(),l=new AbortController,c=setTimeout(()=>l.abort(),1e4),i;try{let u=await fetch(`${Ge}${a}`,{method:t,headers:{Authorization:`Bearer ${s}`,"Content-Type":"application/json"},body:n?JSON.stringify(n):void 0,signal:l.signal});if(i=u.headers.get("x-rid")??void 0,!u.ok)throw Object.assign(new Error(`SeaTalk API error: HTTP ${u.status} (x-rid: ${i})`),{httpStatus:u.status,xRid:i});let m=await u.json();if(m.code===100&&o)return await this.refreshToken(),this.apiCall(t,a,n,!1);if(m.code===101){if(r<Ne.length){let h=Ne[r];return await new Promise(f=>setTimeout(f,h)),this.apiCall(t,a,n,o,r+1)}throw Object.assign(new Error(`SeaTalk rate limit exceeded after ${r+1} attempts (x-rid: ${i})`),{code:101,xRid:i})}if(m.code!==0)throw Object.assign(new Error(`SeaTalk API error: code=${m.code} message=${m.message??"unknown"} (x-rid: ${i})`),{code:m.code,xRid:i});return m}catch(u){throw i&&u instanceof Error&&!u.message.includes("x-rid:")&&(u.message+=` (x-rid: ${i})`),u}finally{clearTimeout(c);}}async sendSingleChat(t,a,n){let o=n?{...a,thread_id:n}:a;await this.apiCall("POST","/messaging/v2/single_chat",{employee_code:t,message:o});}async sendGroupChat(t,a,n){let o=n?{...a,thread_id:n}:a;await this.apiCall("POST","/messaging/v2/group_chat",{group_id:t,message:o});}async setSingleChatTyping(t,a){let n={employee_code:t};a&&(n.thread_id=a),await this.apiCall("POST","/messaging/v2/single_chat_typing",n);}async setGroupChatTyping(t,a){let n={group_id:t};a&&(n.thread_id=a),await this.apiCall("POST","/messaging/v2/group_chat_typing",n);}async downloadMedia(t){let a=await this.getAccessToken(),n=new AbortController,o=setTimeout(()=>n.abort(),6e4);try{let r=await fetch(t,{headers:{Authorization:`Bearer ${a}`},signal:n.signal});if(!r.ok)throw new Error(`SeaTalk media download failed: HTTP ${r.status}`);let s=r.headers.get("content-type")??"application/octet-stream",l=await r.arrayBuffer();return {buffer:Buffer.from(l),contentType:s}}finally{clearTimeout(o);}}async getEmployeeCodeByEmail(t){let n=[];for(let o=0;o<t.length;o+=500){let r=t.slice(o,o+500),s=await this.apiCall("POST","/contacts/v2/get_employee_code_with_email",{emails:r});for(let l of s.employees??[])n.push({email:l.email,employeeCode:l.employee_code,status:l.employee_status});}return n}async getGroupChatHistory(t,a){let n=new URLSearchParams({group_id:t,page_size:String(a?.pageSize??50)});return a?.cursor&&n.set("cursor",a.cursor),this.apiCall("GET",`/messaging/v2/group_chat/history?${n}`)}async getJoinedGroupChats(t){let a=new URLSearchParams;t?.pageSize&&a.set("page_size",String(t.pageSize)),t?.cursor&&a.set("cursor",t.cursor);let n=a.toString();return this.apiCall("GET",`/messaging/v2/group_chat/joined${n?`?${n}`:""}`)}async getGroupChatInfo(t){return this.apiCall("GET",`/messaging/v2/group_chat/info?group_id=${encodeURIComponent(t)}`)}async getDmThread(t,a,n){let o=new URLSearchParams({employee_code:t,thread_id:a});return n?.pageSize&&o.set("page_size",String(n.pageSize)),n?.cursor&&o.set("cursor",n.cursor),this.apiCall("GET",`/messaging/v2/single_chat/get_thread_by_thread_id?${o}`)}async getGroupThread(t,a,n){let o=new URLSearchParams({group_id:t,thread_id:a});return n?.pageSize&&o.set("page_size",String(n.pageSize)),n?.cursor&&o.set("cursor",n.cursor),this.apiCall("GET",`/messaging/v2/group_chat/get_thread_by_thread_id?${o}`)}async getMessageByMessageId(t){return this.apiCall("GET",`/messaging/v2/get_message_by_message_id?message_id=${encodeURIComponent(t)}`)}getAppId(){return this.appId}},He=new Map;});function z(){if(!Je)throw new Error("SeaTalk runtime not initialized");return Je}var Je,me=D(()=>{Je=null;});function qt(e){let t=e&&e.length>0?e:[...Ht];return new Set(t.map(a=>a.trim().toLowerCase()).filter(Boolean))}function zt(e,t){let a;try{a=new URL(e);}catch{return {ok:false,detail:"invalid URL"}}if(a.protocol!=="https:")return {ok:false,detail:`only https allowed (got ${a.protocol})`};let n=a.hostname.toLowerCase();return t.has(n)?{ok:true,hostname:n}:{ok:false,detail:`host not in allowlist (${n})`}}async function re(e){let{message:t,client:a,log:n,mediaAllowHosts:o}=e,r=z(),s=qt(o),l,c,i=Me.file;switch(t.tag){case "image":l=t.image?.content,i=Me.image;break;case "file":l=t.file?.content,c=t.file?.filename;break;case "video":l=t.video?.content,i=Me.video;break;default:return null}if(!l)return null;let u=zt(l,s);if(!u.ok)return n?.(`seatalk: rejected inbound ${t.tag} media before download: ${u.detail}`),null;n?.(`seatalk: inbound ${t.tag} media url host=${u.hostname}`);let m=1;for(let h=0;h<=m;h++)try{let f=await a.downloadMedia(l),d=f.contentType;if((!d||d==="application/octet-stream")&&f.buffer.length<Gt){let p=await r.media.detectMime({buffer:f.buffer});p&&(d=p);}let y=await r.channel.media.saveMediaBuffer(f.buffer,d??"application/octet-stream","inbound",Nt,c);return n?.(`seatalk: downloaded ${t.tag} media, saved to ${y.path}`),{path:y.path,contentType:y.contentType,filename:c,placeholder:i}}catch(f){if(h<m){n?.(`seatalk: retry ${h+1}/${m} downloading ${t.tag} media: ${String(f)}`);continue}return n?.(`seatalk: failed to download ${t.tag} media after ${m+1} attempts: ${String(f)}`),null}return null}async function Wt(e){let t=new AbortController,a=setTimeout(()=>t.abort(),3e4);try{let n=await fetch(e,{signal:t.signal});if(!n.ok)throw new Error(`Failed to fetch media from ${e}: HTTP ${n.status}`);let o=await n.arrayBuffer(),r=Buffer.from(o),s=new URL(e).pathname;return {buffer:r,name:W.basename(s)||"file"}}finally{clearTimeout(a);}}function Kt(e){let t=e.startsWith("~")?W.join(Xe.homedir(),e.slice(1)):e.replace(/^file:\/\//,"");if(!L.existsSync(t))throw new Error(`Media file not found: ${t}`);let a=L.openSync(t,"r");try{let{size:n}=L.fstatSync(a),o=Buffer.alloc(n),r=0;for(;r<n;){let l=L.readSync(a,o,r,n-r,r);if(l===0)break;r+=l;}return {buffer:r<n?o.subarray(0,r):o,name:W.basename(t)}}finally{L.closeSync(a);}}async function Ye(e){let t=e.startsWith("http://")||e.startsWith("https://"),{buffer:a,name:n}=t?await Wt(e):Kt(e);if(a.length>jt)throw new Error(`Media file too large: ${(a.length/1024/1024).toFixed(1)}MB exceeds ~3.75MB limit`);let o=W.extname(n).toLowerCase(),r=Ut.has(o)?"image":"file";return {base64:a.toString("base64"),sendAs:r,filename:r==="file"?n.slice(0,100):void 0}}function xe(e){let t=e[0],a=e.map(o=>o.path),n=e.map(o=>o.contentType).filter(Boolean);return {MediaPath:t?.path,MediaType:t?.contentType,MediaUrl:t?.path,MediaPaths:a.length>0?a:void 0,MediaUrls:a.length>0?a:void 0,MediaTypes:n.length>0?n:void 0}}var Me,Ut,jt,Gt,Nt,Ht,he=D(()=>{me();Me={image:"<media:image>",file:"<media:document>",video:"<media:video>"},Ut=new Set([".png",".jpg",".jpeg",".gif"]),jt=3.75*1024*1024,Gt=10*1024*1024,Nt=250*1024*1024,Ht=["openapi.seatalk.io"];});async function j(e,t,a,n=1,o){await e.sendSingleChat(t,{tag:"text",text:{format:n,content:a}},o);}async function Vt(e,t,a,n){await e.sendSingleChat(t,{tag:"image",image:{content:a}},n);}async function Jt(e,t,a,n,o){await e.sendSingleChat(t,{tag:"file",file:{content:a,filename:n}},o);}async function ee(e,t,a,n=1,o){await e.sendGroupChat(t,{tag:"text",text:{format:n,content:a}},o);}async function ke(e){let{client:t,to:a,mediaUrl:n,threadId:o,isGroup:r}=e,s=await Ye(n);s&&(r?s.sendAs==="image"?await t.sendGroupChat(a,{tag:"image",image:{content:s.base64}},o):await t.sendGroupChat(a,{tag:"file",file:{content:s.base64,filename:s.filename||"file"}},o):s.sendAs==="image"?await Vt(t,a,s.base64,o):await Jt(t,a,s.base64,s.filename||"file",o));}var se=D(()=>{he();});function ct(e){let{groupPolicy:t,groupAllowFrom:a,groupSenderAllowFrom:n,groupId:o,senderEmployeeCode:r,senderEmail:s}=e;return t==="disabled"?{allowed:false,reason:"groupPolicy is disabled"}:t==="allowlist"&&!(a??[]).includes(o)?{allowed:false,reason:`group ${o} not in groupAllowFrom`}:n&&n.length>0&&!n.some(c=>{let i=c.trim();return !!(i==="*"||i===r||s&&i.toLowerCase()===s.toLowerCase())})?{allowed:false,reason:`sender ${r} not in groupSenderAllowFrom`}:{allowed:true}}var dt=D(()=>{});function nn(e){let t=e.sender,a=e.message_sent_time,n=[];return t?.email?n.push(t.email):t?.employee_code&&n.push(t.employee_code),a&&n.push(new Date(a*1e3).toISOString()),n.length>0?`[${n.join(" ")}] `:""}function an(e){let t=e.tag;return {message_id:e.message_id??"",tag:t,text:e.text,image:e.image,file:e.file,video:e.video,combined_forwarded_chat_history:e.combined_forwarded_chat_history}}async function ut(e,t){let a=e.tag,n=[];if(a==="text"){let o=e.text;return {text:o?.plain_text??o?.content??"",media:n}}if(a==="image"||a==="file"||a==="video"){let o=await re({message:an(e),client:t.client,mediaAllowHosts:t.mediaAllowHosts,log:t.log});return o?(n.push(o),{text:o.placeholder,media:n}):{text:`<media:${a}>`,media:n}}if(a==="combined_forwarded_chat_history"){let o=e.combined_forwarded_chat_history?.content;if(o){let r=await ie(o,t);return n.push(...r.media),{text:r.lines.length>0?`[Forwarded messages]
1
+ import {buildChannelConfigSchema,defineSetupPluginEntry,DEFAULT_ACCOUNT_ID,normalizeAccountId,stripChannelTargetPrefix,buildChannelOutboundSessionRoute,buildThreadAwareOutboundSessionRoute,PAIRING_APPROVED_MESSAGE}from'openclaw/plugin-sdk/core';import {getChildLogger}from'openclaw/plugin-sdk/runtime-env';import*as L from'fs';import*as tt from'os';import*as Y from'path';import {resolveSendableOutboundReplyParts,sendMediaWithLeadingCaption}from'openclaw/plugin-sdk/reply-payload';import {createPairingPrefixStripper,createChannelPairingController}from'openclaw/plugin-sdk/channel-pairing';import {resolveDmGroupAccessWithLists,DM_GROUP_ACCESS_REASON}from'openclaw/plugin-sdk/channel-policy';import {resolveThreadSessionKeys}from'openclaw/plugin-sdk/routing';import Bn from'ws';import*as Ae from'crypto';import*as Ft from'http';import {z}from'zod';import {createTopLevelChannelDmPolicy,createStandardChannelSetupStatus,mergeAllowFromEntries}from'openclaw/plugin-sdk/setup';import {normalizeOptionalString}from'openclaw/plugin-sdk/text-runtime';var Ht=Object.defineProperty;var $=(e,t)=>()=>(e&&(t=e(e=0)),t);var Ne=(e,t)=>{for(var o in t)Ht(e,o,{get:t[o],enumerable:true});};function Kt(e){let t=e.channels?.seatalk?.accounts;return !t||typeof t!="object"?[]:Object.keys(t).filter(Boolean)}function ye(e){let t=Kt(e);return t.length===0?[DEFAULT_ACCOUNT_ID]:[...t].toSorted((o,n)=>o.localeCompare(n))}function Me(e){let t=ye(e);return t.includes(DEFAULT_ACCOUNT_ID)?DEFAULT_ACCOUNT_ID:t[0]??DEFAULT_ACCOUNT_ID}function qt(e,t){let o=e.channels?.seatalk?.accounts;if(!(!o||typeof o!="object"))return o[t]}function zt(e,t){let o=e.channels?.seatalk,{accounts:n,...a}=o??{},r=qt(e,t)??{};return {...a,...r}}function H(e){let t=e?.appId?.trim(),o=e?.appSecret?.trim(),n=e?.signingSecret?.trim();return !t||!o||!n?null:{appId:t,appSecret:o,signingSecret:n}}function E(e){let t=normalizeAccountId(e.accountId),n=e.cfg.channels?.seatalk?.enabled!==false,a=zt(e.cfg,t),r=a.enabled!==false,i=n&&r,s=H(a);return {accountId:t,enabled:i,configured:!!s,appId:s?.appId,appSecret:s?.appSecret,mode:a.mode??"webhook",relayUrl:a.relayUrl,webhookPort:a.webhookPort??8080,webhookPath:a.webhookPath??"/callback",tools:a.tools,config:a}}function ke(e){return ye(e).map(t=>E({cfg:e,accountId:t})).filter(t=>t.enabled&&t.configured)}var W=$(()=>{});function C(e){let t=Ke.get(e);if(t)return t;let o=getChildLogger({channel:"seatalk",module:e}),n={debug:(a,r)=>r?o.debug?.(a,r):o.debug?.(a),info:(a,r)=>r?o.info(a,r):o.info(a),warn:(a,r)=>r?o.warn(a,r):o.warn(a),error:(a,r)=>r?o.error(a,r):o.error(a)};return Ke.set(e,n),n}var Ke,V=$(()=>{Ke=new Map;});function Oe(e,t){let o=`${e}:${t}`,n=Ve.get(o);return n||(n=new Re(e,t),Ve.set(o,n)),n}function U(e){return !e.appId||!e.appSecret?null:Oe(e.appId,e.appSecret)}var qe,ze,Vt,We,Re,Ve,ae=$(()=>{V();qe="https://openapi.seatalk.io",ze=1e4,Vt=600,We=[1e4,6e4],Re=class{appId;appSecret;tokenInfo=null;tokenPromise=null;constructor(t,o){this.appId=t,this.appSecret=o;}async getAccessToken(){if(this.tokenInfo){let t=Math.floor(Date.now()/1e3);if(this.tokenInfo.expireAt-t>Vt)return this.tokenInfo.token}return (await this.refreshToken()).token}async refreshToken(){if(this.tokenPromise)return this.tokenPromise;this.tokenPromise=this._fetchToken();try{let t=await this.tokenPromise;return this.tokenInfo=t,t}finally{this.tokenPromise=null;}}async _fetchToken(){let t=new AbortController,o=setTimeout(()=>t.abort(),ze);try{let n=await fetch(`${qe}/auth/app_access_token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({app_id:this.appId,app_secret:this.appSecret}),signal:t.signal});if(!n.ok){let r=n.headers.get("x-rid")??void 0;throw new Error(`SeaTalk token request failed: HTTP ${n.status} (x-rid: ${r})`)}let a=await n.json();if(a.code!==0)throw new Error(`SeaTalk token error: code=${a.code} message=${a.message??"unknown"}`);if(!a.app_access_token||!a.expire)throw new Error("SeaTalk token response missing token or expire");return {token:a.app_access_token,expireAt:a.expire}}finally{clearTimeout(o);}}async apiCall(t,o,n,a=true,r=0){let i=await this.getAccessToken(),s=new AbortController,l=setTimeout(()=>s.abort(),ze),c;try{let d=await fetch(`${qe}${o}`,{method:t,headers:{Authorization:`Bearer ${i}`,"Content-Type":"application/json"},body:n?JSON.stringify(n):void 0,signal:s.signal});if(c=d.headers.get("x-rid")??void 0,!d.ok)throw Object.assign(new Error(`SeaTalk API error: HTTP ${d.status} (x-rid: ${c})`),{httpStatus:d.status,xRid:c});let u=await d.json();if(u.code===100&&a)return await this.refreshToken(),this.apiCall(t,o,n,!1);if(u.code===101){if(r<We.length){let m=We[r];return C("client").warn("rate limited, retrying",{path:o,attempt:r+1,delayMs:m,xRid:c}),await new Promise(p=>setTimeout(p,m)),this.apiCall(t,o,n,a,r+1)}throw Object.assign(new Error(`SeaTalk rate limit exceeded after ${r+1} attempts (x-rid: ${c})`),{code:101,xRid:c})}if(u.code!==0)throw Object.assign(new Error(`SeaTalk API error: code=${u.code} message=${u.message??"unknown"} (x-rid: ${c})`),{code:u.code,xRid:c});return u}catch(d){throw c&&d instanceof Error&&!d.message.includes("x-rid:")&&(d.message+=` (x-rid: ${c})`),d}finally{clearTimeout(l);}}async sendSingleChat(t,o,n){let a=o.tag;C("outbound").info("dm send",{employeeCode:t,tag:a,threadId:n});let r=n?{...o,thread_id:n}:o;await this.apiCall("POST","/messaging/v2/single_chat",{employee_code:t,message:r});}async sendGroupChat(t,o,n){let a=o.tag;C("outbound").info("group send",{groupId:t,tag:a,threadId:n});let r=n?{...o,thread_id:n}:o;await this.apiCall("POST","/messaging/v2/group_chat",{group_id:t,message:r});}async setSingleChatTyping(t,o){let n={employee_code:t};o&&(n.thread_id=o),await this.apiCall("POST","/messaging/v2/single_chat_typing",n);}async setGroupChatTyping(t,o){let n={group_id:t};o&&(n.thread_id=o),await this.apiCall("POST","/messaging/v2/group_chat_typing",n);}async downloadMedia(t){let o=await this.getAccessToken(),n=new AbortController,a=setTimeout(()=>n.abort(),6e4);try{let r=await fetch(t,{headers:{Authorization:`Bearer ${o}`},signal:n.signal});if(!r.ok)throw new Error(`SeaTalk media download failed: HTTP ${r.status}`);let i=r.headers.get("content-type")??"application/octet-stream",s=await r.arrayBuffer();return {buffer:Buffer.from(s),contentType:i}}finally{clearTimeout(a);}}async getEmployeeCodeByEmail(t){let n=[];for(let a=0;a<t.length;a+=500){let r=t.slice(a,a+500),i=await this.apiCall("POST","/contacts/v2/get_employee_code_with_email",{emails:r});for(let s of i.employees??[])n.push({email:s.email,employeeCode:s.employee_code,status:s.employee_status});}return n}async getGroupChatHistory(t,o){let n=new URLSearchParams({group_id:t,page_size:String(o?.pageSize??50)});return o?.cursor&&n.set("cursor",o.cursor),this.apiCall("GET",`/messaging/v2/group_chat/history?${n}`)}async getJoinedGroupChats(t){let o=new URLSearchParams;t?.pageSize&&o.set("page_size",String(t.pageSize)),t?.cursor&&o.set("cursor",t.cursor);let n=o.toString();return this.apiCall("GET",`/messaging/v2/group_chat/joined${n?`?${n}`:""}`)}async getGroupChatInfo(t){return this.apiCall("GET",`/messaging/v2/group_chat/info?group_id=${encodeURIComponent(t)}`)}async getDmThread(t,o,n){let a=new URLSearchParams({employee_code:t,thread_id:o});return n?.pageSize&&a.set("page_size",String(n.pageSize)),n?.cursor&&a.set("cursor",n.cursor),this.apiCall("GET",`/messaging/v2/single_chat/get_thread_by_thread_id?${a}`)}async getGroupThread(t,o,n){let a=new URLSearchParams({group_id:t,thread_id:o});return n?.pageSize&&a.set("page_size",String(n.pageSize)),n?.cursor&&a.set("cursor",n.cursor),this.apiCall("GET",`/messaging/v2/group_chat/get_thread_by_thread_id?${a}`)}async getMessageByMessageId(t){return this.apiCall("GET",`/messaging/v2/get_message_by_message_id?message_id=${encodeURIComponent(t)}`)}getAppId(){return this.appId}},Ve=new Map;});function X(){if(!et)throw new Error("SeaTalk runtime not initialized");return et}var et,Se=$(()=>{et=null;});function nn(e){let t=e&&e.length>0?e:[...tn];return new Set(t.map(o=>o.trim().toLowerCase()).filter(Boolean))}function on(e,t){let o;try{o=new URL(e);}catch{return {ok:false,detail:"invalid URL"}}if(o.protocol!=="https:")return {ok:false,detail:`only https allowed (got ${o.protocol})`};let n=o.hostname.toLowerCase();return t.has(n)?{ok:true,hostname:n}:{ok:false,detail:`host not in allowlist (${n})`}}async function de(e){let{message:t,client:o,mediaAllowHosts:n}=e,a=C("media"),r=X(),i=nn(n),s,l,c=$e.file;switch(t.tag){case "image":s=t.image?.content,c=$e.image;break;case "file":s=t.file?.content,l=t.file?.filename;break;case "video":s=t.video?.content,c=$e.video;break;default:return null}if(!s)return null;let d=on(s,i);if(!d.ok)return a.warn("media rejected",{tag:t.tag,detail:d.detail}),null;a.info("media url",{tag:t.tag,host:d.hostname});let u=1;for(let m=0;m<=u;m++)try{let p=await o.downloadMedia(s),h=p.contentType;if((!h||h==="application/octet-stream")&&p.buffer.length<Zt){let f=await r.media.detectMime({buffer:p.buffer});f&&(h=f);}let y=await r.channel.media.saveMediaBuffer(p.buffer,h??"application/octet-stream","inbound",en,l);return a.info("media downloaded",{tag:t.tag,path:y.path}),{path:y.path,contentType:y.contentType,filename:l,placeholder:c}}catch(p){if(m<u){a.warn("media download retry",{tag:t.tag,attempt:m+1,maxRetry:u,err:String(p)});continue}return a.error("media download failed",{tag:t.tag,attempts:u+1,err:String(p)}),null}return null}async function an(e){let t=new AbortController,o=setTimeout(()=>t.abort(),3e4);try{let n=await fetch(e,{signal:t.signal});if(!n.ok)throw new Error(`Failed to fetch media from ${e}: HTTP ${n.status}`);let a=await n.arrayBuffer(),r=Buffer.from(a),i=new URL(e).pathname;return {buffer:r,name:Y.basename(i)||"file"}}finally{clearTimeout(o);}}function rn(e){let t=e.startsWith("~")?Y.join(tt.homedir(),e.slice(1)):e.replace(/^file:\/\//,"");if(!L.existsSync(t))throw new Error(`Media file not found: ${t}`);let o=L.openSync(t,"r");try{let{size:n}=L.fstatSync(o),a=Buffer.alloc(n),r=0;for(;r<n;){let s=L.readSync(o,a,r,n-r,r);if(s===0)break;r+=s;}return {buffer:r<n?a.subarray(0,r):a,name:Y.basename(t)}}finally{L.closeSync(o);}}async function nt(e){let t=e.startsWith("http://")||e.startsWith("https://"),{buffer:o,name:n}=t?await an(e):rn(e);if(o.length>Qt)throw new Error(`Media file too large: ${(o.length/1024/1024).toFixed(1)}MB exceeds ~3.75MB limit`);let a=Y.extname(n).toLowerCase(),r=Jt.has(a)?"image":"file";return {base64:o.toString("base64"),sendAs:r,filename:r==="file"?n.slice(0,100):void 0}}function Be(e){let t=e[0],o=e.map(a=>a.path),n=e.map(a=>a.contentType).filter(Boolean);return {MediaPath:t?.path,MediaType:t?.contentType,MediaUrl:t?.path,MediaPaths:o.length>0?o:void 0,MediaUrls:o.length>0?o:void 0,MediaTypes:n.length>0?n:void 0}}var $e,Jt,Qt,Zt,en,tn,we=$(()=>{V();Se();$e={image:"<media:image>",file:"<media:document>",video:"<media:video>"},Jt=new Set([".png",".jpg",".jpeg",".gif"]),Qt=3.75*1024*1024,Zt=10*1024*1024,en=250*1024*1024,tn=["openapi.seatalk.io"];});async function G(e,t,o,n=1,a){await e.sendSingleChat(t,{tag:"text",text:{format:n,content:o}},a);}async function sn(e,t,o,n){await e.sendSingleChat(t,{tag:"image",image:{content:o}},n);}async function ln(e,t,o,n,a){await e.sendSingleChat(t,{tag:"file",file:{content:o,filename:n}},a);}async function re(e,t,o,n=1,a){await e.sendGroupChat(t,{tag:"text",text:{format:n,content:o}},a);}async function Te(e){let{client:t,to:o,mediaUrl:n,threadId:a,isGroup:r}=e,i=await nt(n);i&&(r?i.sendAs==="image"?await t.sendGroupChat(o,{tag:"image",image:{content:i.base64}},a):await t.sendGroupChat(o,{tag:"file",file:{content:i.base64,filename:i.filename||"file"}},a):i.sendAs==="image"?await sn(t,o,i.base64,a):await ln(t,o,i.base64,i.filename||"file",a));}var ue=$(()=>{we();});function kt(e){let{groupPolicy:t,groupAllowFrom:o,groupSenderAllowFrom:n,groupId:a,senderEmployeeCode:r,senderEmail:i}=e;return t==="disabled"?{allowed:false,reason:"groupPolicy is disabled"}:t==="allowlist"&&!(o??[]).includes(a)?{allowed:false,reason:`group ${a} not in groupAllowFrom`}:n&&n.length>0&&!n.some(l=>{let c=l.trim();return !!(c==="*"||c===r||i&&c.toLowerCase()===i.toLowerCase())})?{allowed:false,reason:`sender ${r} not in groupSenderAllowFrom`}:{allowed:true}}var St=$(()=>{});function kn(e){let t=e.sender,o=e.message_sent_time,n=[];return t?.email?n.push(t.email):t?.employee_code&&n.push(t.employee_code),o&&n.push(new Date(o*1e3).toISOString()),n.length>0?`[${n.join(" ")}] `:""}function Sn(e){let t=e.tag;return {message_id:e.message_id??"",tag:t,text:e.text,image:e.image,file:e.file,video:e.video,combined_forwarded_chat_history:e.combined_forwarded_chat_history}}async function wt(e,t){let o=e.tag,n=[];if(o==="text"){let a=e.text;return {text:a?.plain_text??a?.content??"",media:n}}if(o==="image"||o==="file"||o==="video"){let a=await de({message:Sn(e),client:t.client,mediaAllowHosts:t.mediaAllowHosts});return a?(n.push(a),{text:a.placeholder,media:n}):{text:`<media:${o}>`,media:n}}if(o==="combined_forwarded_chat_history"){let a=e.combined_forwarded_chat_history?.content;if(a){let r=await ge(a,t);return n.push(...r.media),{text:r.lines.length>0?`[Forwarded messages]
2
2
  ${r.lines.join(`
3
- `)}`:"[Forwarded messages]",media:n}}return {text:"[Forwarded messages]",media:n}}return {text:`<unsupported:${a??"unknown"}>`,media:n}}async function ie(e,t){let a=[],n=[];for(let o of e){if(Array.isArray(o)){let c=await ie(o,t);a.push(...c.lines),n.push(...c.media);continue}if(!o||typeof o!="object")continue;let r=o,s=nn(r),l=await ut(r,t);n.push(...l.media),l.text&&a.push(`${s}${l.text}`);}return {lines:a,media:n}}async function Oe(e){let{client:t,quotedMessageId:a,mediaAllowHosts:n,log:o}=e;try{let r=await t.getMessageByMessageId(a),s=r.sender,l=s?.employee_code??"unknown",c=s?.email?`${l} (${s.email})`:l,i=await ut(r,{client:t,mediaAllowHosts:n,log:o});return {text:`[Quoted from ${c}: ${i.text}]`,media:i.media}}catch(r){return o(`seatalk: failed to resolve quoted message ${a}: ${String(r)}`),null}}async function Fe(e){let{mediaUrls:t,client:a,to:n,threadId:o,isGroup:r,log:s}=e;await sendMediaWithLeadingCaption({mediaUrls:t,caption:"",send:async({mediaUrl:l})=>{await ke({client:a,to:n,mediaUrl:l,threadId:o,isGroup:r});},onError:async({error:l,mediaUrl:c})=>{s(`seatalk: failed to send media ${c}: ${String(l)}`);}});}var gt=D(()=>{he();se();});function De(e){let{send:t,chunkText:a,maxLength:n,joiner:o,idleFlushMs:r}=e,s="",l,c=Promise.resolve(),i=()=>{l&&(clearTimeout(l),l=void 0);},u=()=>{if(!s)return;let d=s;s="";let y=d.length>n?a(d,n):[d],p=async()=>{for(let S of y)await t(S);};c=c.then(p,p),c.catch(()=>{});},m=()=>{!r||r<=0||(i(),l=setTimeout(()=>{l=void 0,u();},r));};return {append:d=>{if(!d)return;if(i(),!s){s=d,m();return}let y=`${s}${o}${d}`;if(y.length>n){u(),s=d,m();return}s=y,m();},flush:async()=>{i(),u(),await c;},hasBuffered:()=>s.length>0}}var pt=D(()=>{});function ln(e,t,a){return a.some(n=>{let o=n.trim();return !!(o==="*"||o===e||t&&o.toLowerCase()===t.toLowerCase())})}function Ce(e){let{cfg:t,event:a,client:n,runtime:o,accountId:r}=e,s=o?.log??console.log,l=o?.error??console.error,c=i=>i().catch(u=>l(`seatalk[${r}]: event error: ${String(u)}`));switch(a.event_type){case "message_from_bot_subscriber":c(()=>kn({cfg:t,event:a,client:n,runtime:o,accountId:r}));break;case "new_mentioned_message_received_from_group_chat":case "new_message_received_from_thread":c(()=>yn({cfg:t,event:a,client:n,runtime:o,accountId:r}));break;case "new_bot_subscriber":{let i=a.event?.employee_code;s(`seatalk[${r}]: new subscriber: ${i}`);break}case "bot_added_to_group_chat":{let i=a.event?.group_id;s(`seatalk[${r}]: bot added to group: ${i}`);break}case "bot_removed_from_group_chat":{let i=a.event?.group_id;s(`seatalk[${r}]: bot removed from group: ${i}`);break}default:s(`seatalk[${r}]: unhandled event type: ${a.event_type}`);}}function ht(e){let t=Date.now();if(t-ft>un){for(let[a,n]of V)t-n>cn&&V.delete(a);ft=t;}if(V.has(e))return false;if(V.size>=dn){let a=V.keys().next().value;V.delete(a);}return V.set(e,t),true}function pn(e,t,a){return a?`${e}:dm:${t}:t:${a}`:`${e}:dm:${t}`}function fn(e,t,a,n){return n?`${e}:grp:${t}:${a}:t:${n}`:`${e}:grp:${t}:${a}`}function mn(e,t){clearTimeout(t.timer);let a=Date.now()-t.firstEventAt,n=gn-a;if(n<=0){Be(e);return}let o=Math.min(kt,n);t.timer=setTimeout(()=>Be(e),o);}function Be(e){let t=be.get(e);if(!t)return;be.delete(e);let a=t.entries;if(a.length===0)return;a[0].kind==="dm"?hn(a,t.context).catch(r=>{(t.context.runtime?.error??console.error)(`seatalk[${t.context.accountId}]: flush error: ${String(r)}`);}):Sn(a,t.context).catch(r=>{(t.context.runtime?.error??console.error)(`seatalk[${t.context.accountId}]: group flush error: ${String(r)}`);});}function St(e,t,a){let n=be.get(e);n||(n={entries:[],timer:setTimeout(()=>Be(e),kt),firstEventAt:Date.now(),context:a},be.set(e,n)),n.entries.push(t),mn(e,n);}async function hn(e,t){let{cfg:a,client:n,runtime:o,accountId:r}=t,s=o?.log??console.log,l=o?.error??console.error,c=e[0].parsedEvent,i=c.employee_code,u=c.email,m=A({cfg:a,accountId:r}),h=m.config,f=z(),d=h?.dmPolicy??"allowlist",y=(h?.allowFrom??[]).map(w=>String(w)),p=createChannelPairingController({core:f,channel:"seatalk",accountId:r}),S=d==="pairing"?await p.readAllowFromStore().catch(()=>[]):[],T=resolveDmGroupAccessWithLists({isGroup:false,dmPolicy:d,groupPolicy:"disabled",allowFrom:y,groupAllowFrom:[],storeAllowFrom:S,isSenderAllowed:w=>ln(i,u,w)});if(T.decision==="pairing"){(await p.issueChallenge({senderId:i,senderIdLine:`Your SeaTalk employee code: ${i}`,meta:u?{email:u}:void 0,onCreated:({code:k})=>{s(`seatalk[${r}]: pairing request sender=${i} code=${k}`);},sendPairingReply:async k=>{await j(n,i,k,1,c.message.thread_id);},onReplyError:k=>{s(`seatalk[${r}]: pairing reply failed for ${i}: ${String(k)}`);}})).created||s(`seatalk[${r}]: pairing already pending for ${i}`);return}if(T.decision!=="allow"){T.reasonCode===DM_GROUP_ACCESS_REASON.DM_POLICY_DISABLED?s(`seatalk[${r}]: blocked DM from ${i} (dmPolicy=disabled)`):s(`seatalk[${r}]: sender ${i} not in allowlist, dropping`);return}let $=h?.mediaAllowHosts,v={client:n,mediaAllowHosts:$,log:s},b=[],P=[];for(let{parsedEvent:w}of e){let k=w.message;switch(k.tag){case "text":(k.text?.plain_text||k.text?.content)&&b.push(k.text.plain_text??k.text.content??"");break;case "image":case "file":case "video":{let I=await re({message:k,client:n,mediaAllowHosts:$,log:s});I&&P.push(I);break}case "combined_forwarded_chat_history":{let I=k.combined_forwarded_chat_history?.content;if(I){let N=await ie(I,v);P.push(...N.media),b.push(N.lines.length>0?`[Forwarded messages]
4
- ${N.lines.join(`
5
- `)}`:"[Forwarded messages]");}break}}}let B=new Set,M=[];for(let{parsedEvent:w}of e){let k=w.message.quoted_message_id;if(!k||B.has(k))continue;B.add(k);let I=await Oe({client:n,quotedMessageId:k,mediaAllowHosts:$,log:s});I&&(M.push(I.text),P.push(...I.media));}let te=xe(P),x=b.join(`
6
- `);if(M.length>0){let w=M.join(`
7
- `);x=x?`${w}
8
- ${x}`:w;}if(!x&&P.length>0&&(x=P.map(w=>w.placeholder).join(" ")),!x&&P.length===0){s(`seatalk[${r}]: skipping empty message from ${i}`);return}let ne=i+(u?` (${u})`:""),C=c.message.message_id,_=c.message.thread_id;try{let w=`seatalk:${i}`,k=i,I=f.channel.routing.resolveAgentRoute({cfg:a,channel:"seatalk",accountId:r,peer:{kind:"direct",id:i}}),N=x.replace(/\s+/g," ").slice(0,160);f.system.enqueueSystemEvent(`SeaTalk[${r}] DM from ${ne}: ${N}`,{sessionKey:I.sessionKey,contextKey:`seatalk:message:${i}:${C}`});let J=e[0].event.timestamp,ae=J?new Date(J*1e3):new Date,$e=f.channel.reply.resolveEnvelopeFormatOptions(a),Ue=`${ne}: ${x}`,ce=f.channel.reply.formatAgentEnvelope({channel:"SeaTalk",from:i,timestamp:ae,envelope:$e,body:Ue}),X={};_&&(X.threadId=_);let oe=c.message.quoted_message_id;oe&&(X.quotedMessageId=oe);let de=f.channel.reply.finalizeInboundContext({Body:ce,BodyForAgent:x,RawBody:x,CommandBody:x,From:w,To:k,SessionKey:I.sessionKey,AccountId:I.accountId,ChatType:"direct",SenderName:ne,SenderId:i,Provider:"seatalk",Surface:"seatalk",MessageSid:C,MessageThreadId:_||void 0,Timestamp:J?J*1e3:Date.now(),WasMentioned:!1,CommandAuthorized:!0,OriginatingChannel:"seatalk",OriginatingTo:k,...Object.keys(X).length>0?{Metadata:X}:{},...te});(m.config?.processingIndicator??"typing")==="typing"&&n.setSingleChatTyping(i,_).catch(O=>s(`seatalk[${r}]: typing failed: ${String(O)}`));let Y=h?.outboundCoalescing!==!1,ue=O=>j(n,i,O,1,_),R=(O,F)=>f.channel.text.chunkMarkdownText(O,F),E=Y?De({send:ue,chunkText:R,maxLength:Te,joiner:`
3
+ `)}`:"[Forwarded messages]",media:n}}return {text:"[Forwarded messages]",media:n}}return {text:`<unsupported:${o??"unknown"}>`,media:n}}async function ge(e,t){let o=[],n=[];for(let a of e){if(Array.isArray(a)){let l=await ge(a,t);o.push(...l.lines),n.push(...l.media);continue}if(!a||typeof a!="object")continue;let r=a,i=kn(r),s=await wt(r,t);n.push(...s.media),s.text&&o.push(`${i}${s.text}`);}return {lines:o,media:n}}async function De(e){let{client:t,quotedMessageId:o,mediaAllowHosts:n}=e;try{let a=await t.getMessageByMessageId(o),r=a.sender,i=r?.employee_code??"unknown",s=r?.email?`${i} (${r.email})`:i,l=await wt(a,{client:t,mediaAllowHosts:n});return {text:`[Quoted from ${s}: ${l.text}]`,media:l.media}}catch(a){return C("inbound").warn("resolve quoted message failed",{quotedMessageId:o,err:String(a)}),null}}async function Le(e){let{mediaUrls:t,client:o,to:n,threadId:a,isGroup:r}=e;await sendMediaWithLeadingCaption({mediaUrls:t,caption:"",send:async({mediaUrl:i})=>{await Te({client:o,to:n,mediaUrl:i,threadId:a,isGroup:r});},onError:async({error:i,mediaUrl:s})=>{C("outbound").error("media delivery failed",{to:n,threadId:a,isGroup:r,mediaUrl:s,err:String(i)});}});}var Tt=$(()=>{V();we();ue();});function Ue(e){let{send:t,chunkText:o,maxLength:n,joiner:a,idleFlushMs:r}=e,i="",s,l=Promise.resolve(),c=()=>{s&&(clearTimeout(s),s=void 0);},d=()=>{if(!i)return;let h=i;i="";let y=h.length>n?o(h,n):[h],f=async()=>{for(let w of y)await t(w);};l=l.then(f,f),l.catch(()=>{});},u=()=>{!r||r<=0||(c(),s=setTimeout(()=>{s=void 0,d();},r));};return {append:h=>{if(!h)return;if(c(),!i){i=h,u();return}let y=`${i}${a}${h}`;if(y.length>n){d(),i=h,u();return}i=y,u();},flush:async()=>{c(),d(),await l;},hasBuffered:()=>i.length>0}}var bt=$(()=>{});function Cn(e,t,o){return o.some(n=>{let a=n.trim();return !!(a==="*"||a===e||t&&a.toLowerCase()===t.toLowerCase())})}function Ie(e){let{cfg:t,event:o,client:n,runtime:a,accountId:r}=e,i=C("event"),s=l=>l().catch(c=>i.error("event handler failed",{accountId:r,eventType:o.event_type,err:String(c)}));switch(o.event_type){case "message_from_bot_subscriber":s(()=>Rn({cfg:t,event:o,client:n,runtime:a,accountId:r}));break;case "new_mentioned_message_received_from_group_chat":case "new_message_received_from_thread":s(()=>On({cfg:t,event:o,client:n,runtime:a,accountId:r}));break;case "new_bot_subscriber":{let l=o.event?.employee_code;i.info("new subscriber",{accountId:r,employeeCode:l});break}case "bot_added_to_group_chat":{let l=o.event?.group_id;i.info("bot added to group",{accountId:r,groupId:l});break}case "bot_removed_from_group_chat":{let l=o.event?.group_id;i.info("bot removed from group",{accountId:r,groupId:l});break}default:i.warn("unhandled event",{accountId:r,eventType:o.event_type});}}function _t(e){let t=Date.now();if(t-Ct>_n){for(let[o,n]of Q)t-n>vn&&Q.delete(o);Ct=t;}if(Q.has(e))return false;if(Q.size>=In){let o=Q.keys().next().value;Q.delete(o);}return Q.set(e,t),true}function Pn(e,t,o){return o?`${e}:dm:${t}:t:${o}`:`${e}:dm:${t}`}function En(e,t,o,n){return n?`${e}:grp:${t}:${o}:t:${n}`:`${e}:grp:${t}:${o}`}function xn(e,t){clearTimeout(t.timer);let o=Date.now()-t.firstEventAt,n=An-o;if(n<=0){Ge(e);return}let a=Math.min(At,n);t.timer=setTimeout(()=>Ge(e),a);}function Ge(e){let t=ve.get(e);if(!t)return;ve.delete(e);let o=t.entries;if(o.length===0)return;o[0].kind==="dm"?Mn(o,t.context).catch(r=>{C("inbound").error("dm flush failed",{accountId:t.context.accountId,err:String(r)});}):$n(o,t.context).catch(r=>{C("inbound").error("group flush failed",{accountId:t.context.accountId,err:String(r)});});}function Et(e,t,o){let n=ve.get(e);n||(n={entries:[],timer:setTimeout(()=>Ge(e),At),firstEventAt:Date.now(),context:o},ve.set(e,n)),n.entries.push(t),xn(e,n);}async function Mn(e,t){let{cfg:o,client:n,accountId:a}=t,r=C("inbound"),i=e[0].parsedEvent,s=i.employee_code,l=i.email,c=E({cfg:o,accountId:a}),d=c.config,u=X(),m=d?.dmPolicy??"allowlist",p=(d?.allowFrom??[]).map(T=>String(T)),h=createChannelPairingController({core:u,channel:"seatalk",accountId:a}),y=m==="pairing"?await h.readAllowFromStore().catch(()=>[]):[],f=resolveDmGroupAccessWithLists({isGroup:false,dmPolicy:m,groupPolicy:"disabled",allowFrom:p,groupAllowFrom:[],storeAllowFrom:y,isSenderAllowed:T=>Cn(s,l,T)});if(f.decision==="pairing"){(await h.issueChallenge({senderId:s,senderIdLine:`Your SeaTalk employee code: ${s}`,meta:l?{email:l}:void 0,onCreated:({code:S})=>{r.info("pairing challenge issued",{accountId:a,employeeCode:s,code:S});},sendPairingReply:async S=>{await G(n,s,S,1,i.message.thread_id);},onReplyError:S=>{r.warn("pairing reply failed",{accountId:a,employeeCode:s,err:String(S)});}})).created||r.info("pairing pending",{accountId:a,employeeCode:s});return}if(f.decision!=="allow"){let T=f.reasonCode===DM_GROUP_ACCESS_REASON.DM_POLICY_DISABLED?"dm policy disabled":"sender not in allowlist";r.warn("dm access denied",{accountId:a,employeeCode:s,reason:T});return}let w=d?.mediaAllowHosts,b={client:n,mediaAllowHosts:w},v=[],k=[];for(let{parsedEvent:T}of e){let S=T.message;switch(S.tag){case "text":(S.text?.plain_text||S.text?.content)&&v.push(S.text.plain_text??S.text.content??"");break;case "image":case "file":case "video":{let x=await de({message:S,client:n,mediaAllowHosts:w});x&&k.push(x);break}case "combined_forwarded_chat_history":{let x=S.combined_forwarded_chat_history?.content;if(x){let F=await ge(x,b);k.push(...F.media),v.push(F.lines.length>0?`[Forwarded messages]
4
+ ${F.lines.join(`
5
+ `)}`:"[Forwarded messages]");}break}}}let j=new Set,A=[];for(let{parsedEvent:T}of e){let S=T.message.quoted_message_id;if(!S||j.has(S))continue;j.add(S);let x=await De({client:n,quotedMessageId:S,mediaAllowHosts:w});x&&(A.push(x.text),k.push(...x.media));}let B=Be(k),R=v.join(`
6
+ `);if(A.length>0){let T=A.join(`
7
+ `);R=R?`${T}
8
+ ${R}`:T;}if(!R&&k.length>0&&(R=k.map(T=>T.placeholder).join(" ")),!R&&k.length===0){r.info("dm empty, skipping",{accountId:a,employeeCode:s});return}let se=s+(l?` (${l})`:""),I=i.message.message_id,_=i.message.thread_id;try{let T=`seatalk:${s}`,S=s,x=u.channel.routing.resolveAgentRoute({cfg:o,channel:"seatalk",accountId:a,peer:{kind:"direct",id:s}}),F=(d?.dmThreadSession??!0)&&!!_,Z=resolveThreadSessionKeys({baseSessionKey:x.sessionKey,threadId:F?_:void 0,parentSessionKey:F&&(d?.threadInheritParent??!0)?x.sessionKey:void 0}),Pe=R.replace(/\s+/g," ").slice(0,160);u.system.enqueueSystemEvent(`SeaTalk[${a}] DM from ${se}: ${Pe}`,{sessionKey:Z.sessionKey,contextKey:`seatalk:message:${s}:${I}`});let ee=e[0].event.timestamp,ie=ee?new Date(ee*1e3):new Date,Ee=u.channel.reply.resolveEnvelopeFormatOptions(o),He=`${se}: ${R}`,te=u.channel.reply.formatAgentEnvelope({channel:"SeaTalk",from:s,timestamp:ie,envelope:Ee,body:He}),ne={};_&&(ne.threadId=_);let le=i.message.quoted_message_id;le&&(ne.quotedMessageId=le);let fe=u.channel.reply.finalizeInboundContext({Body:te,BodyForAgent:R,RawBody:R,CommandBody:R,From:T,To:S,SessionKey:Z.sessionKey,ParentSessionKey:Z.parentSessionKey,AccountId:x.accountId,ChatType:"direct",SenderName:se,SenderId:s,Provider:"seatalk",Surface:"seatalk",MessageSid:I,MessageThreadId:_||void 0,Timestamp:ee?ee*1e3:Date.now(),WasMentioned:!1,CommandAuthorized:!0,OriginatingChannel:"seatalk",OriginatingTo:S,...Object.keys(ne).length>0?{Metadata:ne}:{},...B});(c.config?.processingIndicator??"typing")==="typing"&&n.setSingleChatTyping(s,_).catch(D=>r.warn("dm typing failed",{accountId:a,employeeCode:s,err:String(D)}));let ce=d?.outboundCoalescing!==!1,z=D=>G(n,s,D,1,_),me=(D,O)=>u.channel.text.chunkMarkdownText(D,O),P=ce?Ue({send:z,chunkText:me,maxLength:Ce,joiner:`
9
9
 
10
- `,idleFlushMs:yt}):null,Q=f.channel.reply.createReplyDispatcherWithTyping({humanDelay:f.channel.reply.resolveHumanDelayConfig(a,I.agentId),deliver:async O=>{let F=resolveSendableOutboundReplyParts(O);if(!(!F.hasText&&!F.hasMedia)){if(F.hasText)if(s(`seatalk[${r}]: inline deliver DM to ${i} threadId=${_||"none"}`),E)E.append(F.trimmedText);else {let Pt=R(F.trimmedText,Te);for(let Mt of Pt)await ue(Mt);}F.hasMedia&&(E&&await E.flush(),await Fe({mediaUrls:F.mediaUrls,client:n,to:i,threadId:_,isGroup:!1,log:s}));}},onError:O=>{l(`seatalk[${r}]: reply delivery failed: ${String(O)}`);}}),Ie={agentId:I.agentId,...Q.replyOptions};s(`seatalk[${r}]: dispatching to agent (session=${I.sessionKey})`);try{let{queuedFinal:O,counts:F}=await f.channel.reply.dispatchReplyFromConfig({ctx:de,cfg:a,dispatcher:Q.dispatcher,replyOptions:Ie});s(`seatalk[${r}]: dispatch complete (queuedFinal=${O}, counts=${JSON.stringify(F)})`);}finally{Q.markDispatchIdle(),E&&(await Q.dispatcher.waitForIdle(),await E.flush());}}catch(w){l(`seatalk[${r}]: failed to dispatch message: ${String(w)}`);}}async function kn(e){let{cfg:t,event:a,client:n,runtime:o,accountId:r}=e,s=o?.log??console.log;if(!ht(`${r}:${a.event_id}`)){s(`seatalk[${r}]: skipping duplicate event ${a.event_id}`);return}let l=a.event;if(!l?.employee_code||!l?.message){s(`seatalk[${r}]: malformed message event, skipping`);return}s(`seatalk[${r}]: received ${l.message.tag} from ${l.employee_code} (threadId=${l.message.thread_id||"none"})`);let c=pn(r,l.employee_code,l.message.thread_id);St(c,{kind:"dm",event:a,parsedEvent:l},{cfg:t,client:n,runtime:o,accountId:r});}async function yn(e){let{cfg:t,event:a,client:n,runtime:o,accountId:r}=e,s=o?.log??console.log;if(!ht(`${r}:${a.event_id}`)){s(`seatalk[${r}]: skipping duplicate group event ${a.event_id}`);return}let l=a.event,c=l?.group_id,i=l?.message,u=i?.sender;if(!c||!i||!u?.employee_code){s(`seatalk[${r}]: malformed group message event, skipping`);return}if(u.sender_type===2){s(`seatalk[${r}]: ignoring bot message in group ${c}`);return}let m=u.employee_code,h=u.email,f=i.thread_id;s(`seatalk[${r}]: group ${c} ${i.tag} from ${m} (event=${a.event_type})`);let y=A({cfg:t,accountId:r}).config,p=ct({groupPolicy:y?.groupPolicy??"disabled",groupAllowFrom:y?.groupAllowFrom,groupSenderAllowFrom:y?.groupSenderAllowFrom,groupId:c,senderEmployeeCode:m,senderEmail:h});if(!p.allowed){s(`seatalk[${r}]: group access denied: ${p.reason}`);return}let S=fn(r,c,m,f);St(S,{kind:"group",event:a,groupEvent:l,groupId:c,eventType:a.event_type},{cfg:t,client:n,runtime:o,accountId:r});}async function Sn(e,t){let{cfg:a,client:n,runtime:o,accountId:r}=t,s=o?.log??console.log,l=o?.error??console.error,c=e[0],i=c.groupId,u=c.groupEvent.message,m=u.sender,h=m.employee_code,f=m.email,d=u.thread_id,p=A({cfg:a,accountId:r}).config,S=p?.mediaAllowHosts,T={client:n,mediaAllowHosts:S,log:s},$=[],v=[];for(let{groupEvent:C}of e){let _=C.message;switch(_.tag){case "text":(_.text?.plain_text||_.text?.content)&&$.push(_.text.plain_text??_.text.content??"");break;case "image":case "file":case "video":{let w=await re({message:_,client:n,mediaAllowHosts:S,log:s});w&&v.push(w);break}case "combined_forwarded_chat_history":{let w=_.combined_forwarded_chat_history?.content;if(w){let k=await ie(w,T);v.push(...k.media),$.push(k.lines.length>0?`[Forwarded messages]
11
- ${k.lines.join(`
12
- `)}`:"[Forwarded messages]");}break}}}let b=c.groupEvent.message.quoted_message_id,P=null;if(b){let C=await Oe({client:n,quotedMessageId:b,mediaAllowHosts:S,log:s});C&&(P=C.text,v.push(...C.media));}let B=xe(v),M=$.join(`
13
- `);if(P&&(M=M?`${P}
14
- ${M}`:P),!M&&v.length>0&&(M=v.map(C=>C.placeholder).join(" ")),!M&&v.length===0){s(`seatalk[${r}]: skipping empty group message from ${h} in ${i}`);return}let te=h+(f?` (${f})`:""),x=u.message_id,ne=e.some(C=>C.eventType==="new_mentioned_message_received_from_group_chat");try{let C=z(),_=C.channel.routing.resolveAgentRoute({cfg:a,channel:"seatalk",accountId:r,peer:{kind:"group",id:i}}),w=M.replace(/\s+/g," ").slice(0,160);C.system.enqueueSystemEvent(`SeaTalk[${r}] Group(${i}) from ${te}: ${w}`,{sessionKey:_.sessionKey,contextKey:`seatalk:group:${i}:${x}`});let k=c.groupEvent.message.message_sent_time,I=k?new Date(k*1e3):new Date,N=C.channel.reply.resolveEnvelopeFormatOptions(a),J=C.channel.reply.formatAgentEnvelope({channel:"SeaTalk",from:h,timestamp:I,envelope:N,body:`${te}: ${M}`}),ae={groupId:i};d&&(ae.threadId=d),b&&(ae.quotedMessageId=b);let $e=C.channel.reply.finalizeInboundContext({Body:J,BodyForAgent:M,RawBody:M,CommandBody:M,From:`seatalk:${h}`,To:`group:${i}`,SessionKey:_.sessionKey,AccountId:_.accountId,ChatType:"group",SenderName:te,SenderId:h,Provider:"seatalk",Surface:"seatalk",MessageSid:x,MessageThreadId:d||void 0,Timestamp:k?k*1e3:Date.now(),WasMentioned:ne,CommandAuthorized:!0,OriginatingChannel:"seatalk",OriginatingTo:`group:${i}`,Metadata:ae,...B});(p?.processingIndicator??"typing")==="typing"&&n.setGroupChatTyping(i,d).catch(R=>s(`seatalk[${r}]: group typing failed: ${String(R)}`));let ce=d||void 0,X=p?.outboundCoalescing!==!1,oe=R=>ee(n,i,R,1,ce),de=(R,E)=>C.channel.text.chunkMarkdownText(R,E),H=X?De({send:oe,chunkText:de,maxLength:Te,joiner:`
10
+ `,idleFlushMs:Pt}):null,M=C("outbound"),oe=u.channel.reply.createReplyDispatcherWithTyping({humanDelay:u.channel.reply.resolveHumanDelayConfig(o,x.agentId),deliver:async D=>{let O=resolveSendableOutboundReplyParts(D);if(!(!O.hasText&&!O.hasMedia)){if(O.hasText)if(M.info("dm inline deliver",{accountId:a,employeeCode:s,threadId:_,kind:"text"}),P)P.append(O.trimmedText);else {let Gt=me(O.trimmedText,Ce);for(let jt of Gt)await z(jt);}O.hasMedia&&(M.info("dm inline deliver",{accountId:a,employeeCode:s,threadId:_,kind:"media",count:O.mediaUrls.length}),P&&await P.flush(),await Le({mediaUrls:O.mediaUrls,client:n,to:s,threadId:_,isGroup:!1}));}},onError:D=>{M.error("dm reply delivery failed",{accountId:a,employeeCode:s,err:String(D)});}}),xe={agentId:x.agentId,...oe.replyOptions};r.info("dm dispatching to agent",{accountId:a,employeeCode:s,sessionKey:Z.sessionKey});try{let{queuedFinal:D,counts:O}=await u.channel.reply.dispatchReplyFromConfig({ctx:fe,cfg:o,dispatcher:oe.dispatcher,replyOptions:xe});r.info("dm dispatch complete",{accountId:a,employeeCode:s,queuedFinal:D,counts:O});}finally{oe.markDispatchIdle(),P&&(await oe.dispatcher.waitForIdle(),await P.flush());}}catch(T){r.error("dm dispatch failed",{accountId:a,employeeCode:s,err:String(T)});}}async function Rn(e){let{cfg:t,event:o,client:n,runtime:a,accountId:r}=e,i=C("inbound");if(!_t(`${r}:${o.event_id}`)){i.info("duplicate event skipped",{accountId:r,eventId:o.event_id});return}let s=o.event;if(!s?.employee_code||!s?.message){i.warn("malformed dm event",{accountId:r});return}i.info("dm received",{accountId:r,employeeCode:s.employee_code,tag:s.message.tag,threadId:s.message.thread_id});let l=Pn(r,s.employee_code,s.message.thread_id);Et(l,{kind:"dm",event:o,parsedEvent:s},{cfg:t,client:n,runtime:a,accountId:r});}async function On(e){let{cfg:t,event:o,client:n,runtime:a,accountId:r}=e,i=C("inbound");if(!_t(`${r}:${o.event_id}`)){i.info("duplicate group event skipped",{accountId:r,eventId:o.event_id});return}let s=o.event,l=s?.group_id,c=s?.message,d=c?.sender;if(!l||!c||!d?.employee_code){i.warn("malformed group event",{accountId:r});return}if(d.sender_type===2){i.info("ignoring bot self message",{accountId:r,groupId:l});return}let u=d.employee_code,m=d.email,p=c.thread_id;i.info("group received",{accountId:r,groupId:l,employeeCode:u,tag:c.tag,eventType:o.event_type,threadId:p});let y=E({cfg:t,accountId:r}).config,f=kt({groupPolicy:y?.groupPolicy??"disabled",groupAllowFrom:y?.groupAllowFrom,groupSenderAllowFrom:y?.groupSenderAllowFrom,groupId:l,senderEmployeeCode:u,senderEmail:m});if(!f.allowed){i.warn("group access denied",{accountId:r,groupId:l,employeeCode:u,reason:f.reason});return}let w=En(r,l,u,p);Et(w,{kind:"group",event:o,groupEvent:s,groupId:l,eventType:o.event_type},{cfg:t,client:n,runtime:a,accountId:r});}async function $n(e,t){let{cfg:o,client:n,accountId:a}=t,r=C("inbound"),i=e[0],s=i.groupId,l=i.groupEvent.message,c=l.sender,d=c.employee_code,u=c.email,m=l.thread_id,h=E({cfg:o,accountId:a}).config,y=h?.mediaAllowHosts,f={client:n,mediaAllowHosts:y},w=[],b=[];for(let{groupEvent:I}of e){let _=I.message;switch(_.tag){case "text":(_.text?.plain_text||_.text?.content)&&w.push(_.text.plain_text??_.text.content??"");break;case "image":case "file":case "video":{let T=await de({message:_,client:n,mediaAllowHosts:y});T&&b.push(T);break}case "combined_forwarded_chat_history":{let T=_.combined_forwarded_chat_history?.content;if(T){let S=await ge(T,f);b.push(...S.media),w.push(S.lines.length>0?`[Forwarded messages]
11
+ ${S.lines.join(`
12
+ `)}`:"[Forwarded messages]");}break}}}let v=i.groupEvent.message.quoted_message_id,k=null;if(v){let I=await De({client:n,quotedMessageId:v,mediaAllowHosts:y});I&&(k=I.text,b.push(...I.media));}let j=Be(b),A=w.join(`
13
+ `);if(k&&(A=A?`${k}
14
+ ${A}`:k),!A&&b.length>0&&(A=b.map(I=>I.placeholder).join(" ")),!A&&b.length===0){r.info("group empty, skipping",{accountId:a,groupId:s,employeeCode:d});return}let B=d+(u?` (${u})`:""),R=l.message_id,se=e.some(I=>I.eventType==="new_mentioned_message_received_from_group_chat");try{let I=X(),_=I.channel.routing.resolveAgentRoute({cfg:o,channel:"seatalk",accountId:a,peer:{kind:"group",id:s}}),T=(h?.groupThreadSession??!0)&&!!m,S=resolveThreadSessionKeys({baseSessionKey:_.sessionKey,threadId:T?m:void 0,parentSessionKey:T&&(h?.threadInheritParent??!0)?_.sessionKey:void 0}),x=A.replace(/\s+/g," ").slice(0,160);I.system.enqueueSystemEvent(`SeaTalk[${a}] Group(${s}) from ${B}: ${x}`,{sessionKey:S.sessionKey,contextKey:`seatalk:group:${s}:${R}`});let F=i.groupEvent.message.message_sent_time,Z=F?new Date(F*1e3):new Date,Pe=I.channel.reply.resolveEnvelopeFormatOptions(o),ee=I.channel.reply.formatAgentEnvelope({channel:"SeaTalk",from:d,timestamp:Z,envelope:Pe,body:`${B}: ${A}`}),ie={groupId:s};m&&(ie.threadId=m),v&&(ie.quotedMessageId=v);let Ee=I.channel.reply.finalizeInboundContext({Body:ee,BodyForAgent:A,RawBody:A,CommandBody:A,From:`seatalk:${d}`,To:`group:${s}`,SessionKey:S.sessionKey,ParentSessionKey:S.parentSessionKey,AccountId:_.accountId,ChatType:"group",SenderName:B,SenderId:d,Provider:"seatalk",Surface:"seatalk",MessageSid:R,MessageThreadId:m||void 0,Timestamp:F?F*1e3:Date.now(),WasMentioned:se,CommandAuthorized:!0,OriginatingChannel:"seatalk",OriginatingTo:`group:${s}`,Metadata:ie,...j});(h?.processingIndicator??"typing")==="typing"&&n.setGroupChatTyping(s,m).catch(P=>r.warn("group typing failed",{accountId:a,groupId:s,err:String(P)}));let te=m||void 0,ne=h?.outboundCoalescing!==!1,le=P=>re(n,s,P,1,te),fe=(P,M)=>I.channel.text.chunkMarkdownText(P,M),q=ne?Ue({send:le,chunkText:fe,maxLength:Ce,joiner:`
15
15
 
16
- `,idleFlushMs:yt}):null,Y=C.channel.reply.createReplyDispatcherWithTyping({humanDelay:C.channel.reply.resolveHumanDelayConfig(a,_.agentId),deliver:async R=>{let E=resolveSendableOutboundReplyParts(R);if(!(!E.hasText&&!E.hasMedia)){if(E.hasText)if(H)H.append(E.trimmedText);else {let Q=de(E.trimmedText,Te);for(let Ie of Q)await oe(Ie);}E.hasMedia&&(H&&await H.flush(),await Fe({mediaUrls:E.mediaUrls,client:n,to:i,threadId:ce,isGroup:!0,log:s}));}},onError:R=>{l(`seatalk[${r}]: group reply delivery failed: ${String(R)}`);}}),ue={agentId:_.agentId,...Y.replyOptions};s(`seatalk[${r}]: dispatching group message (session=${_.sessionKey})`);try{let{queuedFinal:R,counts:E}=await C.channel.reply.dispatchReplyFromConfig({ctx:$e,cfg:a,dispatcher:Y.dispatcher,replyOptions:ue});s(`seatalk[${r}]: group dispatch complete (queuedFinal=${R}, counts=${JSON.stringify(E)})`);}finally{Y.markDispatchIdle(),H&&(await Y.dispatcher.waitForIdle(),await H.flush());}}catch(C){l(`seatalk[${r}]: failed to dispatch group message: ${String(C)}`);}}var cn,dn,un,V,ft,kt,gn,Te,yt,be,Le=D(()=>{dt();q();gt();he();pt();me();se();cn=1800*1e3,dn=1e3,un=300*1e3,V=new Map,ft=Date.now();kt=1500,gn=5e3,Te=4e3,yt=1e3,be=new Map;});var Ct={};je(Ct,{connectSeaTalkRelay:()=>vn});function Cn(e,t){return new Promise(a=>{let n=()=>{clearTimeout(o),a();},o=setTimeout(()=>{t?.removeEventListener("abort",n),a();},e);t?.addEventListener("abort",n,{once:true});})}async function bt(e){let{cfg:t,account:a,relayUrl:n,runtime:o,abortSignal:r}=e,{accountId:s}=a,l=o?.log??console.log,c=o?.error??console.error,i=G(a.config)?.signingSecret;if(!a.appId||!a.appSecret||!i)throw new Error(`SeaTalk account "${s}" missing credentials for relay mode`);let u=U(a);if(!u)throw new Error(`SeaTalk client not available for account "${s}"`);let m=wt;for(;!r?.aborted;){try{await new Promise((h,f)=>{if(r?.aborted){h();return}l(`seatalk[${s}]: connecting to relay ${n}...`);let d=new wn(n),y,p=()=>{y&&(clearTimeout(y),y=void 0);},S=()=>{p(),y=setTimeout(()=>{c(`seatalk[${s}]: relay silent for ${Tt}ms, terminating`),d.terminate();},Tt);},T=()=>{p(),d.close(),h();};r?.addEventListener("abort",T,{once:!0}),d.on("upgrade",v=>{v.socket.setKeepAlive(!0,6e4);}),d.on("open",()=>{S(),l(`seatalk[${s}]: relay connected, authenticating...`),d.send(JSON.stringify({type:"auth",appId:a.appId,appSecret:a.appSecret,signingSecret:i}));});let $=!1;d.on("message",v=>{S();let b;try{b=JSON.parse(String(v));}catch{c(`seatalk[${s}]: relay sent invalid JSON`);return}if(!$){b.type==="auth_ok"?($=!0,m=wt,l(`seatalk[${s}]: relay authenticated`)):b.type==="auth_fail"&&(c(`seatalk[${s}]: relay auth failed: ${b.error}`),d.close(),f(new Error(`Relay auth failed: ${b.error}`)));return}switch(b.type){case "event":b.event&&u&&Ce({cfg:t,event:b.event,client:u,runtime:o,accountId:s});break;case "ping":d.send(JSON.stringify({type:"pong"}));break;case "replaced":l(`seatalk[${s}]: connection replaced by another instance`),d.close(),h();return;default:l(`seatalk[${s}]: unknown relay message type: ${b.type}`);}}),d.on("close",(v,b)=>{p(),r?.removeEventListener("abort",T),$&&l(`seatalk[${s}]: relay disconnected (code=${v}, reason=${String(b)})`),h();}),d.on("error",v=>{p(),r?.removeEventListener("abort",T),c(`seatalk[${s}]: relay connection error: ${String(v)}`),h();});});}catch(h){let f=String(h);if(f.includes("Relay auth failed"))throw h;c(`seatalk[${s}]: relay error: ${f}`);}if(r?.aborted)break;l(`seatalk[${s}]: reconnecting in ${m}ms...`),await Cn(m,r),m=Math.min(m*bn,Tn);}}async function vn(e){let t=e.config;if(!t)throw new Error("Config is required for SeaTalk relay client");let a=e.runtime?.log??console.log;if(e.accountId){let o=A({cfg:t,accountId:e.accountId});if(!o.enabled||!o.configured)throw new Error(`SeaTalk account "${e.accountId}" not configured or disabled`);return bt({cfg:t,account:o,relayUrl:e.relayUrl,runtime:e.runtime,abortSignal:e.abortSignal})}let n=fe(t);if(n.length===0)throw new Error("No enabled SeaTalk accounts configured");a(`seatalk: connecting ${n.length} account(s) to relay: ${n.map(o=>o.accountId).join(", ")}`),await Promise.all(n.map(o=>bt({cfg:t,account:o,relayUrl:e.relayUrl,runtime:e.runtime,abortSignal:e.abortSignal})));}var wt,Tn,bn,Tt,vt=D(()=>{q();Le();Z();wt=1e3,Tn=3e4,bn=2,Tt=75e3;});var It={};je(It,{monitorSeaTalkProvider:()=>En});function _n(e,t,a){let n=Buffer.from(t,"latin1"),o=_e.createHash("sha256").update(Buffer.concat([e,n])).digest("hex");try{return _e.timingSafeEqual(Buffer.from(o,"hex"),Buffer.from(a,"hex"))}catch{return false}}function In(e){return new Promise((t,a)=>{let n=0,o=[];e.on("data",r=>{if(n+=r.length,n>$n){e.destroy(new ve);return}o.push(r);}),e.on("end",()=>t(Buffer.concat(o))),e.on("error",a);})}async function _t(e){let{cfg:t,account:a,runtime:n,abortSignal:o}=e,{accountId:r}=a,s=n?.log??console.log,l=n?.error??console.error,c=a.webhookPort,i=a.webhookPath,u=G(a.config)?.signingSecret;if(!u)throw new Error(`SeaTalk account "${r}" missing signingSecret`);let m=U(a);if(!m)throw new Error(`SeaTalk client not available for account "${r}"`);s(`seatalk[${r}]: starting webhook server on port ${c}, path ${i}...`);let h=$t.createServer();return h.on("request",async(f,d)=>{let y=new URL(f.url??"/",`http://localhost:${c}`).pathname;if(f.method!=="POST"||y!==i){d.writeHead(404),d.end("Not Found");return}try{let p=await In(f),S=f.headers.signature;if(!S||!_n(p,u,S)){s(`seatalk[${r}]: signature verification failed`),d.writeHead(403),d.end("Forbidden");return}let T=JSON.parse(p.toString("utf-8"));if(T.event_type==="event_verification"){let $=T.event?.seatalk_challenge;d.writeHead(200,{"Content-Type":"application/json"}),d.end(JSON.stringify({seatalk_challenge:$})),s(`seatalk[${r}]: URL verification challenge responded`);return}d.writeHead(200),d.end("OK"),Ce({cfg:t,event:T,client:m,runtime:n,accountId:r});}catch(p){l(`seatalk[${r}]: request processing error: ${String(p)}`),d.headersSent||(p instanceof ve?(d.writeHead(413),d.end("Payload Too Large")):(d.writeHead(500),d.end("Internal Server Error")));}}),new Promise((f,d)=>{let y=()=>{h.close();},p=()=>{s(`seatalk[${r}]: abort signal received, stopping webhook server`),y(),f();};if(o?.aborted){y(),f();return}o?.addEventListener("abort",p,{once:true}),h.listen(c,()=>{s(`seatalk[${r}]: webhook server listening on port ${c}`);}),h.on("error",S=>{l(`seatalk[${r}]: webhook server error: ${S}`),o?.removeEventListener("abort",p),d(S);});})}async function En(e={}){let t=e.config;if(!t)throw new Error("Config is required for SeaTalk monitor");let a=e.runtime?.log??console.log;if(e.accountId){let o=A({cfg:t,accountId:e.accountId});if(!o.enabled||!o.configured)throw new Error(`SeaTalk account "${e.accountId}" not configured or disabled`);return _t({cfg:t,account:o,runtime:e.runtime,abortSignal:e.abortSignal})}let n=fe(t);if(n.length===0)throw new Error("No enabled SeaTalk accounts configured");a(`seatalk: starting ${n.length} account(s): ${n.map(o=>o.accountId).join(", ")}`),await Promise.all(n.map(o=>_t({cfg:t,account:o,runtime:e.runtime,abortSignal:e.abortSignal})));}var $n,ve,Et=D(()=>{q();Le();Z();$n=1024*1024,ve=class extends Error{constructor(){super("Request body too large"),this.name="PayloadTooLargeError";}};});q();Z();var qe=z$1.enum(["open","allowlist","pairing"]),ze=z$1.enum(["webhook","relay"]),We=z$1.enum(["disabled","allowlist","open"]),Ke=z$1.enum(["typing","off"]),Bt=z$1.object({groupInfo:z$1.boolean().optional().default(true),groupHistory:z$1.boolean().optional().default(true),groupList:z$1.boolean().optional().default(true),threadHistory:z$1.boolean().optional().default(true),getMessage:z$1.boolean().optional().default(true)}).strict(),Lt=z$1.object({enabled:z$1.boolean().optional(),appId:z$1.string().optional(),appSecret:z$1.string().optional(),signingSecret:z$1.string().optional(),mode:ze.optional(),relayUrl:z$1.string().optional(),webhookPort:z$1.number().int().positive().optional(),webhookPath:z$1.string().optional(),dmPolicy:qe.optional(),allowFrom:z$1.array(z$1.string()).optional(),groupPolicy:We.optional(),groupAllowFrom:z$1.array(z$1.string()).optional(),groupSenderAllowFrom:z$1.array(z$1.string()).optional(),processingIndicator:Ke.optional(),mediaAllowHosts:z$1.array(z$1.string()).optional(),outboundCoalescing:z$1.boolean().optional()}).strict(),Ve=z$1.object({enabled:z$1.boolean().optional(),appId:z$1.string().optional(),appSecret:z$1.string().optional(),signingSecret:z$1.string().optional(),mode:ze.optional().default("webhook"),relayUrl:z$1.string().optional(),webhookPort:z$1.number().int().positive().optional().default(8080),webhookPath:z$1.string().optional().default("/callback"),dmPolicy:qe.optional().default("allowlist"),allowFrom:z$1.array(z$1.string()).optional(),groupPolicy:We.optional().default("disabled"),groupAllowFrom:z$1.array(z$1.string()).optional(),groupSenderAllowFrom:z$1.array(z$1.string()).optional(),processingIndicator:Ke.optional().default("typing"),mediaAllowHosts:z$1.array(z$1.string()).optional(),outboundCoalescing:z$1.boolean().optional().default(true),tools:Bt.optional(),accounts:z$1.record(z$1.string(),Lt.optional()).optional()}).strict().superRefine((e,t)=>{e.dmPolicy==="open"&&((e.allowFrom??[]).some(o=>o.trim()==="*")||t.addIssue({code:z$1.ZodIssueCode.custom,path:["allowFrom"],message:'channels.seatalk.dmPolicy="open" requires channels.seatalk.allowFrom to include "*"'}));});q();Z();me();se();var Qe="group:";function Ze(e){let t=e.trim();return t||null}function ye(e){return e.startsWith(Qe)}function Se(e){return e.slice(Qe.length)}function K(e){return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e.trim())}function et(e){let t=e.trim();return t?K(t)?true:ye(t)?Se(t).length>0:/^[a-zA-Z0-9_-]+$/.test(t):false}function tt(e,t){let a=A({cfg:e,accountId:t}),n=U(a);if(!n)throw new Error(`SeaTalk client not available for account ${a.accountId}`);return n}async function nt(e,t){if(!K(t))return t;let n=(await e.getEmployeeCodeByEmail([t])).find(o=>o.employeeCode&&o.status===2);if(n?.employeeCode)return n.employeeCode;throw new Error(`No active SeaTalk employee found for email: ${t}`)}function at(e){if(e!=null)return String(e)}var ot={deliveryMode:"direct",chunker:(e,t)=>z().channel.text.chunkMarkdownText(e,t),chunkerMode:"markdown",textChunkLimit:4e3,sendText:async({cfg:e,to:t,text:a,accountId:n,threadId:o})=>{let r=tt(e,n??void 0),s=at(o);if(ye(t)){let c=Se(t);return await ee(r,c,a,1,s),{channel:"seatalk",messageId:"",chatId:t}}let l=await nt(r,t);return await j(r,l,a,1,s),{channel:"seatalk",messageId:"",chatId:l}},sendMedia:async({cfg:e,to:t,text:a,mediaUrl:n,accountId:o,threadId:r})=>{let s=tt(e,o??void 0),l=at(r),c=ye(t),i=c?Se(t):await nt(s,t);if(a?.trim()&&(c?await ee(s,i,a,1,l):await j(s,i,a,1,l)),n)try{await ke({client:s,to:i,mediaUrl:n,threadId:l,isGroup:c});}catch(u){let m=`[Media send failed: ${u instanceof Error?u.message:String(u)}]`;c?await ee(s,i,m,2,l):await j(s,i,m,2,l);}return {channel:"seatalk",messageId:"",chatId:c?t:i}}};Z();async function we(e){if(!e?.appId||!e?.appSecret)return {ok:false,error:"missing credentials (appId, appSecret)"};try{let t=Pe(e.appId,e.appSecret),a=Date.now();await t.getAccessToken();let n=Date.now()-a;return {ok:!0,appId:e.appId,latencyMs:n}}catch(t){return {ok:false,appId:e.appId,error:t instanceof Error?t.message:String(t)}}}se();q();var st="seatalk";function Re(e){return e.split(/[\n,;]+/g).map(t=>t.trim()).filter(Boolean)}async function it(e){let{cfg:t,prompter:a}=e,n=t.channels?.seatalk?.allowFrom??[];for(await a.note(["Allowlist SeaTalk DMs by email or employee_code.","Examples:","- alice@company.com","- 12345678"].join(`
17
- `),"SeaTalk allowlist");;){let o=await a.text({message:"SeaTalk allowFrom (emails or employee_codes)",placeholder:"alice@company.com, 12345678",initialValue:n[0]?String(n[0]):void 0,validate:l=>String(l??"").trim()?void 0:"Required"}),r=Re(String(o));if(r.length===0){await a.note("Enter at least one user.","SeaTalk allowlist");continue}let s=mergeAllowFromEntries(n.map(l=>String(l)),r);return {...t,channels:{...t.channels,seatalk:{...t.channels?.seatalk,allowFrom:s}}}}}var Zt=createTopLevelChannelDmPolicy({label:"SeaTalk",channel:st,policyKey:"channels.seatalk.dmPolicy",allowFromKey:"channels.seatalk.allowFrom",getCurrent:e=>e.channels?.seatalk?.dmPolicy??"allowlist",promptAllowFrom:async({cfg:e,prompter:t})=>it({cfg:e,prompter:t})});async function rt(e){let t=String(await e.text({message:"Enter SeaTalk App ID",validate:o=>o?.trim()?void 0:"Required"})).trim(),a=String(await e.text({message:"Enter SeaTalk App Secret",validate:o=>o?.trim()?void 0:"Required"})).trim(),n=String(await e.text({message:"Enter SeaTalk Signing Secret",validate:o=>o?.trim()?void 0:"Required"})).trim();return {appId:t,appSecret:a,signingSecret:n}}async function en(e){await e.note(["1) Go to SeaTalk Open Platform (open.seatalk.io)","2) Create a Bot App","3) Get App ID and App Secret from Basic Info & Credentials","4) Get Signing Secret from Event Callback settings","5) Enable Bot capability and set status to Online",'6) Enable "Send Message to Bot User" permission'].join(`
18
- `),"SeaTalk credentials");}var lt={channel:st,status:createStandardChannelSetupStatus({channelLabel:"SeaTalk",configuredLabel:"configured",unconfiguredLabel:"needs app credentials",configuredHint:"configured",unconfiguredHint:"needs app creds",configuredScore:2,unconfiguredScore:0,resolveConfigured:({cfg:e})=>{let t=e.channels?.seatalk;return !!G(t)}}),credentials:[],finalize:async({cfg:e,prompter:t,forceAllowFrom:a})=>{let n=e,o=n.channels?.seatalk,r=G(o),s=!!(o?.appId?.trim()&&o?.appSecret?.trim()&&o?.signingSecret?.trim()),l=null,c=null,i=null;if(r||await en(t),s?await t.confirm({message:"SeaTalk credentials already configured. Keep them?",initialValue:true})||({appId:l,appSecret:c,signingSecret:i}=await rt(t)):{appId:l,appSecret:c,signingSecret:i}=await rt(t),l&&c&&i){n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,enabled:true,appId:l,appSecret:c,signingSecret:i,dmPolicy:o?.dmPolicy??"allowlist"}}};try{let p=await we({appId:l,appSecret:c});p.ok?await t.note(`Connected successfully (latency: ${p.latencyMs}ms)`,"SeaTalk connection test"):await t.note(`Connection failed: ${p.error??"unknown error"}`,"SeaTalk connection test");}catch(p){await t.note(`Connection test failed: ${String(p)}`,"SeaTalk connection test");}await t.note(["Important reminders:",'- Bot App must be set to "Online" status in SeaTalk Open Platform','- "Send Message to Bot User" permission must be enabled',"- Configure the callback URL in Event Callback settings"].join(`
19
- `),"SeaTalk setup");}let u=n.channels?.seatalk?.mode??"webhook",m=await t.select({message:"Gateway mode",options:[{value:"webhook",label:"Webhook \u2014 receive event callbacks directly (default)"},{value:"relay",label:"Relay \u2014 connect to a relay service as client"}],initialValue:u}),h=String(m);if(n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,mode:h}}},h==="relay"){let p=n.channels?.seatalk?.relayUrl??"",S=await t.text({message:"Relay WebSocket URL",placeholder:"wss://relay.example.com/ws",initialValue:p||void 0,validate:$=>{let v=String($??"").trim();if(!v)return "Required";if(!v.startsWith("ws://")&&!v.startsWith("wss://"))return "Must be a ws:// or wss:// URL"}}),T=String(S).trim();T.startsWith("ws://")&&await t.note("ws:// transmits credentials (appSecret, signingSecret) unencrypted. Consider using wss:// for production.","Security warning"),n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,relayUrl:T}}};}else {let p=n.channels?.seatalk?.webhookPort??8080,S=await t.text({message:"Webhook port",initialValue:String(p),validate:P=>{let B=Number(P);return B>0&&B<65536?void 0:"Enter a valid port number (1-65535)"}}),T=Number(S);T&&T!==p&&(n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,webhookPort:T}}});let $=n.channels?.seatalk?.webhookPath??"/callback",v=await t.text({message:"Webhook path",initialValue:$,validate:P=>{let B=String(P??"").trim();if(!B)return "Required";if(!B.startsWith("/"))return "Path must start with /"}}),b=String(v??$).trim();b&&b!==$&&(n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,webhookPath:b}}});}let f=await t.select({message:"Group chat policy",options:[{value:"disabled",label:"Disabled \u2014 ignore all group messages (default)"},{value:"allowlist",label:"Allowlist \u2014 respond only in specific groups"},{value:"open",label:"Open \u2014 respond in all groups the bot joins"}],initialValue:n.channels?.seatalk?.groupPolicy??"disabled"}),d=String(f);if(n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,groupPolicy:d}}},d==="allowlist"){let p=n.channels?.seatalk?.groupAllowFrom??[],S=await t.text({message:"Allowed group IDs (comma-separated)",placeholder:"group_abc123, group_def456",initialValue:p.length>0?p.join(", "):void 0,validate:T=>String(T??"").trim()?void 0:"Enter at least one group ID"});n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,groupAllowFrom:Re(String(S))}}};}if(d!=="disabled"&&await t.confirm({message:"Restrict which users can trigger the bot in groups? (sender allowlist)",initialValue:true})){let S=n.channels?.seatalk?.groupSenderAllowFrom??[],T=await t.text({message:"Sender allowlist (emails or employee_codes, comma-separated)",placeholder:"alice@company.com, 12345678",initialValue:S.length>0?S.join(", "):void 0,validate:$=>String($??"").trim()?void 0:"Enter at least one user"});n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,groupSenderAllowFrom:Re(String(T))}}};}let y=await t.select({message:"Processing indicator",options:[{value:"typing",label:"Typing \u2014 show typing status while processing (default)"},{value:"off",label:"Off \u2014 no processing indicator"}],initialValue:n.channels?.seatalk?.processingIndicator??"typing"});return n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,processingIndicator:String(y)}}},a&&(n=await it({cfg:n,prompter:t})),{cfg:n}},dmPolicy:Zt,disable:e=>({...e,channels:{...e.channels,seatalk:{...e.channels?.seatalk,enabled:false}}})};var xn={id:"seatalk",label:"SeaTalk",selectionLabel:"SeaTalk (plugin)",blurb:"SeaTalk internal messaging integration.",docsPath:"/channels/seatalk",aliases:[],order:70,quickstartAllowFrom:true},At={id:"seatalk",meta:xn,pairing:{idLabel:"employeeCode",normalizeAllowEntry:createPairingPrefixStripper(/^(seatalk|st):/i),notifyApproval:async({cfg:e,id:t})=>{let a=Ee(e),n=A({cfg:e,accountId:a}),o=U(n);o&&await j(o,t,PAIRING_APPROVED_MESSAGE,1);}},capabilities:{chatTypes:["direct","group"],polls:false,threads:true,media:true,reactions:false,edit:false,reply:false},reload:{configPrefixes:["channels.seatalk"]},configSchema:buildChannelConfigSchema(Ve),config:{listAccountIds:e=>pe(e),resolveAccount:(e,t)=>A({cfg:e,accountId:t}),defaultAccountId:e=>Ee(e),setAccountEnabled:({cfg:e,accountId:t,enabled:a})=>{if(t===DEFAULT_ACCOUNT_ID)return {...e,channels:{...e.channels,seatalk:{...e.channels?.seatalk,enabled:a}}};let o=e.channels?.seatalk;return {...e,channels:{...e.channels,seatalk:{...o,accounts:{...o?.accounts,[t]:{...o?.accounts?.[t],enabled:a}}}}}},deleteAccount:({cfg:e,accountId:t})=>{if(t===DEFAULT_ACCOUNT_ID){let r={...e},s={...e.channels};s.seatalk=void 0;let l=Object.values(s).some(c=>c!==void 0);return r.channels=l?s:void 0,r}let n=e.channels?.seatalk,o={...n?.accounts};return delete o[t],{...e,channels:{...e.channels,seatalk:{...n,accounts:Object.keys(o).length>0?o:void 0}}}},isConfigured:e=>e.configured,describeAccount:e=>({accountId:e.accountId,enabled:e.enabled,configured:e.configured,appId:e.appId,mode:e.mode,...e.mode==="relay"?{relayUrl:e.relayUrl}:{webhookPort:e.webhookPort}}),resolveAllowFrom:({cfg:e,accountId:t})=>(A({cfg:e,accountId:t}).config?.allowFrom??[]).map(n=>String(n)),formatAllowFrom:({allowFrom:e})=>e.map(t=>String(t).trim()).filter(Boolean)},security:{collectWarnings:({cfg:e,accountId:t})=>{let a=A({cfg:e,accountId:t});return (a.config?.dmPolicy??"allowlist")!=="open"?[]:[`- SeaTalk[${a.accountId}]: dmPolicy="open" allows any subscriber to message the bot. Set channels.seatalk.dmPolicy to "allowlist" or "pairing" to restrict senders.`]}},setup:{resolveAccountId:()=>DEFAULT_ACCOUNT_ID,applyAccountConfig:({cfg:e,accountId:t})=>{if(!t||t===DEFAULT_ACCOUNT_ID)return {...e,channels:{...e.channels,seatalk:{...e.channels?.seatalk,enabled:true}}};let n=e.channels?.seatalk;return {...e,channels:{...e.channels,seatalk:{...n,accounts:{...n?.accounts,[t]:{...n?.accounts?.[t],enabled:true}}}}}}},setupWizard:lt,messaging:{normalizeTarget:e=>Ze(e)??void 0,targetResolver:{looksLikeId:et,hint:"<employee_code> or <email>"}},resolver:{resolveTargets:async({cfg:e,accountId:t,inputs:a})=>{let n=a.filter(c=>K(c));if(n.length===0)return a.map(c=>({input:c,resolved:true,id:c}));let o=c=>a.map(i=>{let u=K(i);return {input:i,resolved:!u,id:u?void 0:i,note:u?c:void 0}}),r=A({cfg:e,accountId:t}),s=U(r);if(!s)return o("SeaTalk client not available");let l=new Map;try{let c=await s.getEmployeeCodeByEmail(n);for(let i of c)i.employeeCode&&i.status===2&&l.set(i.email.toLowerCase(),i.employeeCode);}catch{return o("Failed to resolve email")}return a.map(c=>{if(!K(c))return {input:c,resolved:true,id:c};let i=l.get(c.toLowerCase());return i?{input:c,resolved:true,id:i,name:c}:{input:c,resolved:false,note:"No active employee found for this email"}})}},outbound:ot,status:{defaultRuntime:{accountId:DEFAULT_ACCOUNT_ID,running:false,lastStartAt:null,lastStopAt:null,lastError:null,port:null},buildChannelSummary:({snapshot:e})=>({configured:e.configured??false,running:e.running??false,lastStartAt:e.lastStartAt??null,lastStopAt:e.lastStopAt??null,lastError:e.lastError??null,port:e.port??null,probe:e.probe,lastProbeAt:e.lastProbeAt??null}),probeAccount:({account:e})=>we(e),buildAccountSnapshot:({account:e,runtime:t,probe:a})=>({accountId:e.accountId,enabled:e.enabled,configured:e.configured,appId:e.appId,mode:e.mode,...e.mode==="relay"?{relayUrl:e.relayUrl}:{webhookPort:e.webhookPort},running:t?.running??false,lastStartAt:t?.lastStartAt??null,lastStopAt:t?.lastStopAt??null,lastError:t?.lastError??null,port:t?.port??null,probe:a})},gateway:{startAccount:async e=>{let t=A({cfg:e.cfg,accountId:e.accountId});if(t.mode==="relay"){if(!t.relayUrl)throw new Error(`SeaTalk account "${e.accountId}" mode=relay but relayUrl is not configured`);e.setStatus({accountId:e.accountId,mode:"relay"}),e.log?.info(`starting seatalk[${e.accountId}] (relay client \u2192 ${t.relayUrl})`);let{connectSeaTalkRelay:o}=await Promise.resolve().then(()=>(vt(),Ct));return o({config:e.cfg,runtime:e.runtime,abortSignal:e.abortSignal,accountId:e.accountId,relayUrl:t.relayUrl})}e.setStatus({accountId:e.accountId,port:t.webhookPort}),e.log?.info(`starting seatalk[${e.accountId}] (webhook on port ${t.webhookPort})`);let{monitorSeaTalkProvider:n}=await Promise.resolve().then(()=>(Et(),It));return n({config:e.cfg,runtime:e.runtime,abortSignal:e.abortSignal,accountId:e.accountId})}}};var Ha=defineSetupPluginEntry(At);
20
- export{Ha as default};
16
+ `,idleFlushMs:Pt}):null,ce=C("outbound"),z=I.channel.reply.createReplyDispatcherWithTyping({humanDelay:I.channel.reply.resolveHumanDelayConfig(o,_.agentId),deliver:async P=>{let M=resolveSendableOutboundReplyParts(P);if(!(!M.hasText&&!M.hasMedia)){if(M.hasText)if(ce.info("group inline deliver",{accountId:a,groupId:s,threadId:te,kind:"text"}),q)q.append(M.trimmedText);else {let oe=fe(M.trimmedText,Ce);for(let xe of oe)await le(xe);}M.hasMedia&&(ce.info("group inline deliver",{accountId:a,groupId:s,threadId:te,kind:"media",count:M.mediaUrls.length}),q&&await q.flush(),await Le({mediaUrls:M.mediaUrls,client:n,to:s,threadId:te,isGroup:!0}));}},onError:P=>{ce.error("group reply delivery failed",{accountId:a,groupId:s,err:String(P)});}}),me={agentId:_.agentId,...z.replyOptions};r.info("group dispatching to agent",{accountId:a,groupId:s,sessionKey:S.sessionKey});try{let{queuedFinal:P,counts:M}=await I.channel.reply.dispatchReplyFromConfig({ctx:Ee,cfg:o,dispatcher:z.dispatcher,replyOptions:me});r.info("group dispatch complete",{accountId:a,groupId:s,queuedFinal:P,counts:M});}finally{z.markDispatchIdle(),q&&(await z.dispatcher.waitForIdle(),await q.flush());}}catch(I){r.error("group dispatch failed",{accountId:a,groupId:s,err:String(I)});}}var vn,In,_n,Q,Ct,At,An,Ce,Pt,ve,je=$(()=>{St();W();Tt();V();we();bt();Se();ue();vn=1800*1e3,In=1e3,_n=300*1e3,Q=new Map,Ct=Date.now();At=1500,An=5e3,Ce=4e3,Pt=1e3,ve=new Map;});var Ot={};Ne(Ot,{connectSeaTalkRelay:()=>Un});function Ln(e,t){return new Promise(o=>{let n=()=>{clearTimeout(a),o();},a=setTimeout(()=>{t?.removeEventListener("abort",n),o();},e);t?.addEventListener("abort",n,{once:true});})}async function Rt(e){let{cfg:t,account:o,relayUrl:n,runtime:a,abortSignal:r}=e,{accountId:i}=o,s=C("relay"),l=H(o.config)?.signingSecret;if(!o.appId||!o.appSecret||!l)throw new Error(`SeaTalk account "${i}" missing credentials for relay mode`);let c=U(o);if(!c)throw new Error(`SeaTalk client not available for account "${i}"`);let d=xt;for(;!r?.aborted;){try{await new Promise((u,m)=>{if(r?.aborted){u();return}s.info("connecting",{accountId:i,relayUrl:n});let p=new Bn(n),h,y=()=>{h&&(clearTimeout(h),h=void 0);},f=()=>{y(),h=setTimeout(()=>{s.error("relay silent, terminating",{accountId:i,timeoutMs:Mt}),p.terminate();},Mt);},w=()=>{y(),p.close(),u();};r?.addEventListener("abort",w,{once:!0}),p.on("upgrade",v=>{v.socket.setKeepAlive(!0,6e4);}),p.on("open",()=>{f(),s.info("connected, authenticating",{accountId:i}),p.send(JSON.stringify({type:"auth",appId:o.appId,appSecret:o.appSecret,signingSecret:l}));});let b=!1;p.on("message",v=>{f();let k;try{k=JSON.parse(String(v));}catch{s.warn("invalid relay json",{accountId:i});return}if(!b){k.type==="auth_ok"?(b=!0,d=xt,s.info("authenticated",{accountId:i})):k.type==="auth_fail"&&(s.error("auth failed",{accountId:i,err:k.error}),p.close(),m(new Error(`Relay auth failed: ${k.error}`)));return}switch(k.type){case "event":k.event&&c&&Ie({cfg:t,event:k.event,client:c,runtime:a,accountId:i});break;case "ping":p.send(JSON.stringify({type:"pong"}));break;case "replaced":s.warn("connection replaced",{accountId:i}),p.close(),u();return;default:s.warn("unknown relay message",{accountId:i,type:k.type});}}),p.on("close",(v,k)=>{y(),r?.removeEventListener("abort",w),b&&s.info("disconnected",{accountId:i,code:v,reason:String(k)}),u();}),p.on("error",v=>{y(),r?.removeEventListener("abort",w),s.error("connection error",{accountId:i,err:String(v)}),u();});});}catch(u){let m=String(u);if(m.includes("Relay auth failed"))throw u;s.error("relay error",{accountId:i,err:m});}if(r?.aborted)break;s.info("reconnecting",{accountId:i,backoffMs:d}),await Ln(d,r),d=Math.min(d*Dn,Fn);}}async function Un(e){let t=e.config;if(!t)throw new Error("Config is required for SeaTalk relay client");let o=C("relay");if(e.accountId){let a=E({cfg:t,accountId:e.accountId});if(!a.enabled||!a.configured)throw new Error(`SeaTalk account "${e.accountId}" not configured or disabled`);return Rt({cfg:t,account:a,relayUrl:e.relayUrl,runtime:e.runtime,abortSignal:e.abortSignal})}let n=ke(t);if(n.length===0)throw new Error("No enabled SeaTalk accounts configured");o.info("connecting accounts",{count:n.length,accountIds:n.map(a=>a.accountId)}),await Promise.all(n.map(a=>Rt({cfg:t,account:a,relayUrl:e.relayUrl,runtime:e.runtime,abortSignal:e.abortSignal})));}var xt,Fn,Dn,Mt,$t=$(()=>{W();je();ae();V();xt=1e3,Fn=3e4,Dn=2,Mt=75e3;});var Dt={};Ne(Dt,{monitorSeaTalkProvider:()=>Nn});function Gn(e,t,o){let n=Buffer.from(t,"latin1"),a=Ae.createHash("sha256").update(Buffer.concat([e,n])).digest("hex");try{return Ae.timingSafeEqual(Buffer.from(a,"hex"),Buffer.from(o,"hex"))}catch{return false}}function Hn(e){return new Promise((t,o)=>{let n=0,a=[];e.on("data",r=>{if(n+=r.length,n>jn){e.destroy(new _e);return}a.push(r);}),e.on("end",()=>t(Buffer.concat(a))),e.on("error",o);})}async function Bt(e){let{cfg:t,account:o,runtime:n,abortSignal:a}=e,{accountId:r}=o,i=C("webhook"),s=o.webhookPort,l=o.webhookPath,c=H(o.config)?.signingSecret;if(!c)throw new Error(`SeaTalk account "${r}" missing signingSecret`);let d=U(o);if(!d)throw new Error(`SeaTalk client not available for account "${r}"`);i.info("starting webhook",{accountId:r,port:s,path:l});let u=Ft.createServer();return u.on("request",async(m,p)=>{let h=new URL(m.url??"/",`http://localhost:${s}`).pathname;if(m.method!=="POST"||h!==l){p.writeHead(404),p.end("Not Found");return}try{let y=await Hn(m),f=m.headers.signature;if(!f||!Gn(y,c,f)){i.warn("signature verification failed",{accountId:r}),p.writeHead(403),p.end("Forbidden");return}let w=JSON.parse(y.toString("utf-8"));if(w.event_type==="event_verification"){let b=w.event?.seatalk_challenge;p.writeHead(200,{"Content-Type":"application/json"}),p.end(JSON.stringify({seatalk_challenge:b})),i.info("url verification responded",{accountId:r});return}p.writeHead(200),p.end("OK"),Ie({cfg:t,event:w,client:d,runtime:n,accountId:r});}catch(y){i.error("request processing error",{accountId:r,err:String(y)}),p.headersSent||(y instanceof _e?(p.writeHead(413),p.end("Payload Too Large")):(p.writeHead(500),p.end("Internal Server Error")));}}),new Promise((m,p)=>{let h=()=>{u.close();},y=()=>{i.info("abort received, stopping webhook",{accountId:r}),h(),m();};if(a?.aborted){h(),m();return}a?.addEventListener("abort",y,{once:true}),u.listen(s,()=>{i.info("webhook listening",{accountId:r,port:s});}),u.on("error",f=>{i.error("webhook server error",{accountId:r,err:String(f)}),a?.removeEventListener("abort",y),p(f);});})}async function Nn(e={}){let t=e.config;if(!t)throw new Error("Config is required for SeaTalk monitor");let o=C("webhook");if(e.accountId){let a=E({cfg:t,accountId:e.accountId});if(!a.enabled||!a.configured)throw new Error(`SeaTalk account "${e.accountId}" not configured or disabled`);return Bt({cfg:t,account:a,runtime:e.runtime,abortSignal:e.abortSignal})}let n=ke(t);if(n.length===0)throw new Error("No enabled SeaTalk accounts configured");o.info("starting accounts",{count:n.length,accountIds:n.map(a=>a.accountId)}),await Promise.all(n.map(a=>Bt({cfg:t,account:a,runtime:e.runtime,abortSignal:e.abortSignal})));}var jn,_e,Lt=$(()=>{W();je();ae();V();jn=1024*1024,_e=class extends Error{constructor(){super("Request body too large"),this.name="PayloadTooLargeError";}};});W();ae();var Xe=z.enum(["open","allowlist","pairing"]),Ye=z.enum(["webhook","relay"]),Je=z.enum(["disabled","allowlist","open"]),Qe=z.enum(["typing","off"]),Xt=z.object({groupInfo:z.boolean().optional().default(true),groupHistory:z.boolean().optional().default(true),groupList:z.boolean().optional().default(true),threadHistory:z.boolean().optional().default(true),getMessage:z.boolean().optional().default(true)}).strict(),Yt=z.object({enabled:z.boolean().optional(),appId:z.string().optional(),appSecret:z.string().optional(),signingSecret:z.string().optional(),mode:Ye.optional(),relayUrl:z.string().optional(),webhookPort:z.number().int().positive().optional(),webhookPath:z.string().optional(),dmPolicy:Xe.optional(),allowFrom:z.array(z.string()).optional(),dmThreadSession:z.boolean().optional(),groupPolicy:Je.optional(),groupAllowFrom:z.array(z.string()).optional(),groupSenderAllowFrom:z.array(z.string()).optional(),groupThreadSession:z.boolean().optional(),threadInheritParent:z.boolean().optional(),processingIndicator:Qe.optional(),mediaAllowHosts:z.array(z.string()).optional(),outboundCoalescing:z.boolean().optional()}).strict(),Ze=z.object({enabled:z.boolean().optional(),appId:z.string().optional(),appSecret:z.string().optional(),signingSecret:z.string().optional(),mode:Ye.optional().default("webhook"),relayUrl:z.string().optional(),webhookPort:z.number().int().positive().optional().default(8080),webhookPath:z.string().optional().default("/callback"),dmPolicy:Xe.optional().default("allowlist"),allowFrom:z.array(z.string()).optional(),dmThreadSession:z.boolean().optional().default(true),groupPolicy:Je.optional().default("disabled"),groupAllowFrom:z.array(z.string()).optional(),groupSenderAllowFrom:z.array(z.string()).optional(),groupThreadSession:z.boolean().optional().default(true),threadInheritParent:z.boolean().optional().default(true),processingIndicator:Qe.optional().default("typing"),mediaAllowHosts:z.array(z.string()).optional(),outboundCoalescing:z.boolean().optional().default(true),tools:Xt.optional(),accounts:z.record(z.string(),Yt.optional()).optional()}).strict().superRefine((e,t)=>{e.dmPolicy==="open"&&((e.allowFrom??[]).some(a=>a.trim()==="*")||t.addIssue({code:z.ZodIssueCode.custom,path:["allowFrom"],message:'channels.seatalk.dmPolicy="open" requires channels.seatalk.allowFrom to include "*"'}));});W();ae();Se();ue();var ot="group:";function at(e){let t=e.trim();return t||null}function N(e){return e.startsWith(ot)}function K(e){return e.slice(ot.length)}function J(e){return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e.trim())}function rt(e){let t=e.trim();return t?J(t)?true:N(t)?K(t).length>0:/^[a-zA-Z0-9_-]+$/.test(t):false}function st(e,t){let o=E({cfg:e,accountId:t}),n=U(o);if(!n)throw new Error(`SeaTalk client not available for account ${o.accountId}`);return n}async function it(e,t){if(!J(t))return t;let n=(await e.getEmployeeCodeByEmail([t])).find(a=>a.employeeCode&&a.status===2);if(n?.employeeCode)return n.employeeCode;throw new Error(`No active SeaTalk employee found for email: ${t}`)}function lt(e){if(e!=null)return String(e)}var ct={deliveryMode:"direct",chunker:(e,t)=>X().channel.text.chunkMarkdownText(e,t),chunkerMode:"markdown",textChunkLimit:4e3,sendText:async({cfg:e,to:t,text:o,accountId:n,threadId:a})=>{let r=st(e,n??void 0),i=lt(a);if(N(t)){let l=K(t);return await re(r,l,o,1,i),{channel:"seatalk",messageId:"",chatId:t}}let s=await it(r,t);return await G(r,s,o,1,i),{channel:"seatalk",messageId:"",chatId:s}},sendMedia:async({cfg:e,to:t,text:o,mediaUrl:n,accountId:a,threadId:r})=>{let i=st(e,a??void 0),s=lt(r),l=N(t),c=l?K(t):await it(i,t);if(o?.trim()&&(l?await re(i,c,o,1,s):await G(i,c,o,1,s)),n)try{await Te({client:i,to:c,mediaUrl:n,threadId:s,isGroup:l});}catch(d){let u=`[Media send failed: ${d instanceof Error?d.message:String(d)}]`;l?await re(i,c,u,2,s):await G(i,c,u,2,s);}return {channel:"seatalk",messageId:"",chatId:l?t:c}}};ae();async function be(e){if(!e?.appId||!e?.appSecret)return {ok:false,error:"missing credentials (appId, appSecret)"};try{let t=Oe(e.appId,e.appSecret),o=Date.now();await t.getAccessToken();let n=Date.now()-o;return {ok:!0,appId:e.appId,latencyMs:n}}catch(t){return {ok:false,appId:e.appId,error:t instanceof Error?t.message:String(t)}}}ue();function dt(e){let t=stripChannelTargetPrefix(e.target,"seatalk");if(!t)return null;let o=N(t),n=o?K(t):t;if(!n)return null;let a=buildChannelOutboundSessionRoute({cfg:e.cfg,agentId:e.agentId,channel:"seatalk",accountId:e.accountId,peer:{kind:o?"group":"direct",id:n},chatType:o?"group":"direct",from:o?`seatalk:group:${n}`:`seatalk:${n}`,to:o?`group:${n}`:n});return buildThreadAwareOutboundSessionRoute({route:a,replyToId:e.replyToId,threadId:e.threadId,currentSessionKey:e.currentSessionKey,canRecoverCurrentThread:({route:r})=>r.chatType!=="direct"||(e.cfg.session?.dmScope??"main")!=="main"})}W();var gt="seatalk";function Fe(e){return e.split(/[\n,;]+/g).map(t=>t.trim()).filter(Boolean)}async function pt(e){let{cfg:t,prompter:o}=e,n=t.channels?.seatalk?.allowFrom??[];for(await o.note(["Allowlist SeaTalk DMs by email or employee_code.","Examples:","- alice@company.com","- 12345678"].join(`
17
+ `),"SeaTalk allowlist");;){let a=await o.text({message:"SeaTalk allowFrom (emails or employee_codes)",placeholder:"alice@company.com, 12345678",initialValue:n[0]?String(n[0]):void 0,validate:s=>String(s??"").trim()?void 0:"Required"}),r=Fe(String(a));if(r.length===0){await o.note("Enter at least one user.","SeaTalk allowlist");continue}let i=mergeAllowFromEntries(n.map(s=>String(s)),r);return {...t,channels:{...t.channels,seatalk:{...t.channels?.seatalk,allowFrom:i}}}}}var mn=createTopLevelChannelDmPolicy({label:"SeaTalk",channel:gt,policyKey:"channels.seatalk.dmPolicy",allowFromKey:"channels.seatalk.allowFrom",getCurrent:e=>e.channels?.seatalk?.dmPolicy??"allowlist",promptAllowFrom:async({cfg:e,prompter:t})=>pt({cfg:e,prompter:t})});async function ut(e){let t=String(await e.text({message:"Enter SeaTalk App ID",validate:a=>a?.trim()?void 0:"Required"})).trim(),o=String(await e.text({message:"Enter SeaTalk App Secret",validate:a=>a?.trim()?void 0:"Required"})).trim(),n=String(await e.text({message:"Enter SeaTalk Signing Secret",validate:a=>a?.trim()?void 0:"Required"})).trim();return {appId:t,appSecret:o,signingSecret:n}}async function hn(e){await e.note(["1) Go to SeaTalk Open Platform (open.seatalk.io)","2) Create a Bot App","3) Get App ID and App Secret from Basic Info & Credentials","4) Get Signing Secret from Event Callback settings","5) Enable Bot capability and set status to Online",'6) Enable "Send Message to Bot User" permission'].join(`
18
+ `),"SeaTalk credentials");}var ft={channel:gt,status:createStandardChannelSetupStatus({channelLabel:"SeaTalk",configuredLabel:"configured",unconfiguredLabel:"needs app credentials",configuredHint:"configured",unconfiguredHint:"needs app creds",configuredScore:2,unconfiguredScore:0,resolveConfigured:({cfg:e})=>{let t=e.channels?.seatalk;return !!H(t)}}),credentials:[],finalize:async({cfg:e,prompter:t,forceAllowFrom:o})=>{let n=e,a=n.channels?.seatalk,r=H(a),i=!!(a?.appId?.trim()&&a?.appSecret?.trim()&&a?.signingSecret?.trim()),s=null,l=null,c=null;if(r||await hn(t),i?await t.confirm({message:"SeaTalk credentials already configured. Keep them?",initialValue:true})||({appId:s,appSecret:l,signingSecret:c}=await ut(t)):{appId:s,appSecret:l,signingSecret:c}=await ut(t),s&&l&&c){n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,enabled:true,appId:s,appSecret:l,signingSecret:c,dmPolicy:a?.dmPolicy??"allowlist"}}};try{let f=await be({appId:s,appSecret:l});f.ok?await t.note(`Connected successfully (latency: ${f.latencyMs}ms)`,"SeaTalk connection test"):await t.note(`Connection failed: ${f.error??"unknown error"}`,"SeaTalk connection test");}catch(f){await t.note(`Connection test failed: ${String(f)}`,"SeaTalk connection test");}await t.note(["Important reminders:",'- Bot App must be set to "Online" status in SeaTalk Open Platform','- "Send Message to Bot User" permission must be enabled',"- Configure the callback URL in Event Callback settings"].join(`
19
+ `),"SeaTalk setup");}let d=n.channels?.seatalk?.mode??"webhook",u=await t.select({message:"Gateway mode",options:[{value:"webhook",label:"Webhook \u2014 receive event callbacks directly (default)"},{value:"relay",label:"Relay \u2014 connect to a relay service as client"}],initialValue:d}),m=String(u);if(n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,mode:m}}},m==="relay"){let f=n.channels?.seatalk?.relayUrl??"",w=await t.text({message:"Relay WebSocket URL",placeholder:"wss://relay.example.com/ws",initialValue:f||void 0,validate:v=>{let k=String(v??"").trim();if(!k)return "Required";if(!k.startsWith("ws://")&&!k.startsWith("wss://"))return "Must be a ws:// or wss:// URL"}}),b=String(w).trim();b.startsWith("ws://")&&await t.note("ws:// transmits credentials (appSecret, signingSecret) unencrypted. Consider using wss:// for production.","Security warning"),n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,relayUrl:b}}};}else {let f=n.channels?.seatalk?.webhookPort??8080,w=await t.text({message:"Webhook port",initialValue:String(f),validate:A=>{let B=Number(A);return B>0&&B<65536?void 0:"Enter a valid port number (1-65535)"}}),b=Number(w);b&&b!==f&&(n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,webhookPort:b}}});let v=n.channels?.seatalk?.webhookPath??"/callback",k=await t.text({message:"Webhook path",initialValue:v,validate:A=>{let B=String(A??"").trim();if(!B)return "Required";if(!B.startsWith("/"))return "Path must start with /"}}),j=String(k??v).trim();j&&j!==v&&(n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,webhookPath:j}}});}let p=await t.select({message:"Group chat policy",options:[{value:"disabled",label:"Disabled \u2014 ignore all group messages (default)"},{value:"allowlist",label:"Allowlist \u2014 respond only in specific groups"},{value:"open",label:"Open \u2014 respond in all groups the bot joins"}],initialValue:n.channels?.seatalk?.groupPolicy??"disabled"}),h=String(p);if(n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,groupPolicy:h}}},h==="allowlist"){let f=n.channels?.seatalk?.groupAllowFrom??[],w=await t.text({message:"Allowed group IDs (comma-separated)",placeholder:"group_abc123, group_def456",initialValue:f.length>0?f.join(", "):void 0,validate:b=>String(b??"").trim()?void 0:"Enter at least one group ID"});n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,groupAllowFrom:Fe(String(w))}}};}if(h!=="disabled"&&await t.confirm({message:"Restrict which users can trigger the bot in groups? (sender allowlist)",initialValue:true})){let w=n.channels?.seatalk?.groupSenderAllowFrom??[],b=await t.text({message:"Sender allowlist (emails or employee_codes, comma-separated)",placeholder:"alice@company.com, 12345678",initialValue:w.length>0?w.join(", "):void 0,validate:v=>String(v??"").trim()?void 0:"Enter at least one user"});n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,groupSenderAllowFrom:Fe(String(b))}}};}let y=await t.select({message:"Processing indicator",options:[{value:"typing",label:"Typing \u2014 show typing status while processing (default)"},{value:"off",label:"Off \u2014 no processing indicator"}],initialValue:n.channels?.seatalk?.processingIndicator??"typing"});return n={...n,channels:{...n.channels,seatalk:{...n.channels?.seatalk,processingIndicator:String(y)}}},o&&(n=await pt({cfg:n,prompter:t})),{cfg:n}},dmPolicy:mn,disable:e=>({...e,channels:{...e.channels,seatalk:{...e.channels?.seatalk,enabled:false}}})};function ht(e){let t=e.context.To,o=t?.startsWith("group:")?t.slice(6):normalizeOptionalString(t),n=typeof e.context.MessageThreadId=="number"?String(e.context.MessageThreadId):normalizeOptionalString(e.context.MessageThreadId);return {currentChannelId:o,currentThreadTs:n,replyToMode:"all",hasRepliedRef:e.hasRepliedRef}}function yt(e){let t=e.toolContext;if(!(!t?.currentThreadTs||!t.currentChannelId||(N(e.to)?K(e.to):e.to)!==t.currentChannelId))return t.currentThreadTs}var Wn={id:"seatalk",label:"SeaTalk",selectionLabel:"SeaTalk (plugin)",blurb:"SeaTalk internal messaging integration.",docsPath:"/channels/seatalk",aliases:[],order:70,quickstartAllowFrom:true},Ut={id:"seatalk",meta:Wn,pairing:{idLabel:"employeeCode",normalizeAllowEntry:createPairingPrefixStripper(/^(seatalk|st):/i),notifyApproval:async({cfg:e,id:t})=>{let o=Me(e),n=E({cfg:e,accountId:o}),a=U(n);a&&await G(a,t,PAIRING_APPROVED_MESSAGE,1);}},capabilities:{chatTypes:["direct","group"],polls:false,threads:true,media:true,reactions:false,edit:false,reply:false},reload:{configPrefixes:["channels.seatalk"]},configSchema:buildChannelConfigSchema(Ze),config:{listAccountIds:e=>ye(e),resolveAccount:(e,t)=>E({cfg:e,accountId:t}),defaultAccountId:e=>Me(e),setAccountEnabled:({cfg:e,accountId:t,enabled:o})=>{if(t===DEFAULT_ACCOUNT_ID)return {...e,channels:{...e.channels,seatalk:{...e.channels?.seatalk,enabled:o}}};let a=e.channels?.seatalk;return {...e,channels:{...e.channels,seatalk:{...a,accounts:{...a?.accounts,[t]:{...a?.accounts?.[t],enabled:o}}}}}},deleteAccount:({cfg:e,accountId:t})=>{if(t===DEFAULT_ACCOUNT_ID){let r={...e},i={...e.channels};i.seatalk=void 0;let s=Object.values(i).some(l=>l!==void 0);return r.channels=s?i:void 0,r}let n=e.channels?.seatalk,a={...n?.accounts};return delete a[t],{...e,channels:{...e.channels,seatalk:{...n,accounts:Object.keys(a).length>0?a:void 0}}}},isConfigured:e=>e.configured,describeAccount:e=>({accountId:e.accountId,enabled:e.enabled,configured:e.configured,appId:e.appId,mode:e.mode,...e.mode==="relay"?{relayUrl:e.relayUrl}:{webhookPort:e.webhookPort}}),resolveAllowFrom:({cfg:e,accountId:t})=>(E({cfg:e,accountId:t}).config?.allowFrom??[]).map(n=>String(n)),formatAllowFrom:({allowFrom:e})=>e.map(t=>String(t).trim()).filter(Boolean)},security:{collectWarnings:({cfg:e,accountId:t})=>{let o=E({cfg:e,accountId:t});return (o.config?.dmPolicy??"allowlist")!=="open"?[]:[`- SeaTalk[${o.accountId}]: dmPolicy="open" allows any subscriber to message the bot. Set channels.seatalk.dmPolicy to "allowlist" or "pairing" to restrict senders.`]}},setup:{resolveAccountId:()=>DEFAULT_ACCOUNT_ID,applyAccountConfig:({cfg:e,accountId:t})=>{if(!t||t===DEFAULT_ACCOUNT_ID)return {...e,channels:{...e.channels,seatalk:{...e.channels?.seatalk,enabled:true}}};let n=e.channels?.seatalk;return {...e,channels:{...e.channels,seatalk:{...n,accounts:{...n?.accounts,[t]:{...n?.accounts?.[t],enabled:true}}}}}}},setupWizard:ft,messaging:{normalizeTarget:e=>at(e)??void 0,resolveOutboundSessionRoute:e=>dt(e),targetResolver:{looksLikeId:rt,hint:"<employee_code> or <email>"}},threading:{buildToolContext:e=>ht(e),resolveAutoThreadId:e=>yt(e)},resolver:{resolveTargets:async({cfg:e,accountId:t,inputs:o})=>{let n=o.filter(l=>J(l));if(n.length===0)return o.map(l=>({input:l,resolved:true,id:l}));let a=l=>o.map(c=>{let d=J(c);return {input:c,resolved:!d,id:d?void 0:c,note:d?l:void 0}}),r=E({cfg:e,accountId:t}),i=U(r);if(!i)return a("SeaTalk client not available");let s=new Map;try{let l=await i.getEmployeeCodeByEmail(n);for(let c of l)c.employeeCode&&c.status===2&&s.set(c.email.toLowerCase(),c.employeeCode);}catch{return a("Failed to resolve email")}return o.map(l=>{if(!J(l))return {input:l,resolved:true,id:l};let c=s.get(l.toLowerCase());return c?{input:l,resolved:true,id:c,name:l}:{input:l,resolved:false,note:"No active employee found for this email"}})}},outbound:ct,status:{defaultRuntime:{accountId:DEFAULT_ACCOUNT_ID,running:false,lastStartAt:null,lastStopAt:null,lastError:null,port:null},buildChannelSummary:({snapshot:e})=>({configured:e.configured??false,running:e.running??false,lastStartAt:e.lastStartAt??null,lastStopAt:e.lastStopAt??null,lastError:e.lastError??null,port:e.port??null,probe:e.probe,lastProbeAt:e.lastProbeAt??null}),probeAccount:({account:e})=>be(e),buildAccountSnapshot:({account:e,runtime:t,probe:o})=>({accountId:e.accountId,enabled:e.enabled,configured:e.configured,appId:e.appId,mode:e.mode,...e.mode==="relay"?{relayUrl:e.relayUrl}:{webhookPort:e.webhookPort},running:t?.running??false,lastStartAt:t?.lastStartAt??null,lastStopAt:t?.lastStopAt??null,lastError:t?.lastError??null,port:t?.port??null,probe:o})},gateway:{startAccount:async e=>{let t=E({cfg:e.cfg,accountId:e.accountId});if(t.mode==="relay"){if(!t.relayUrl)throw new Error(`SeaTalk account "${e.accountId}" mode=relay but relayUrl is not configured`);e.setStatus({accountId:e.accountId,mode:"relay"}),e.log?.info(`starting seatalk[${e.accountId}] (relay client \u2192 ${t.relayUrl})`);let{connectSeaTalkRelay:a}=await Promise.resolve().then(()=>($t(),Ot));return a({config:e.cfg,runtime:e.runtime,abortSignal:e.abortSignal,accountId:e.accountId,relayUrl:t.relayUrl})}e.setStatus({accountId:e.accountId,port:t.webhookPort}),e.log?.info(`starting seatalk[${e.accountId}] (webhook on port ${t.webhookPort})`);let{monitorSeaTalkProvider:n}=await Promise.resolve().then(()=>(Lt(),Dt));return n({config:e.cfg,runtime:e.runtime,abortSignal:e.abortSignal,accountId:e.accountId})}}};var ba=defineSetupPluginEntry(Ut);
20
+ export{ba as default};
@@ -19,9 +19,12 @@
19
19
  "webhookPath": { "type": "string" },
20
20
  "dmPolicy": { "type": "string", "enum": ["open", "allowlist", "pairing"] },
21
21
  "allowFrom": { "type": "array", "items": { "type": "string" } },
22
+ "dmThreadSession": { "type": "boolean" },
22
23
  "groupPolicy": { "type": "string", "enum": ["disabled", "allowlist", "open"] },
23
24
  "groupAllowFrom": { "type": "array", "items": { "type": "string" } },
24
25
  "groupSenderAllowFrom": { "type": "array", "items": { "type": "string" } },
26
+ "groupThreadSession": { "type": "boolean" },
27
+ "threadInheritParent": { "type": "boolean" },
25
28
  "outboundCoalescing": { "type": "boolean" },
26
29
  "processingIndicator": { "type": "string", "enum": ["typing", "off"] },
27
30
  "mediaAllowHosts": { "type": "array", "items": { "type": "string" } },
@@ -53,6 +56,7 @@
53
56
  "enum": ["open", "allowlist", "pairing"]
54
57
  },
55
58
  "allowFrom": { "type": "array", "items": { "type": "string" } },
59
+ "dmThreadSession": { "type": "boolean" },
56
60
  "groupPolicy": {
57
61
  "type": "string",
58
62
  "enum": ["disabled", "allowlist", "open"]
@@ -65,6 +69,8 @@
65
69
  "type": "array",
66
70
  "items": { "type": "string" }
67
71
  },
72
+ "groupThreadSession": { "type": "boolean" },
73
+ "threadInheritParent": { "type": "boolean" },
68
74
  "outboundCoalescing": { "type": "boolean" },
69
75
  "processingIndicator": {
70
76
  "type": "string",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-seatalk",
3
- "version": "0.3.3",
3
+ "version": "0.4.0",
4
4
  "description": "OpenClaw SeaTalk channel plugin",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",