openclaw-seatalk 0.3.2 → 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 ADDED
@@ -0,0 +1,68 @@
1
+ # Changelog
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
+
9
+ ## 0.3.3
10
+
11
+ - Publish bundled JS only (`dist/*.js`), fixing install failure on OpenClaw versions that require compiled runtime output for TypeScript entries.
12
+ - Re-declare `openclaw.setupEntry` so the channel appears in onboarding before configuration.
13
+ - Drop `SEATALK_*` env-var credential fallback.
14
+
15
+ ## 0.3.2
16
+
17
+ - Detect half-open relay connections: reconnect after 75s of inbound silence; enable TCP keep-alive (60s).
18
+
19
+ ## 0.3.1
20
+
21
+ - Update OpenClaw compatibility and remove SDK deprecation warnings.
22
+
23
+ ## 0.3.0
24
+
25
+ - Support forwarded messages.
26
+ - Outbound coalescing: consecutive reply payloads are merged into a single message with automatic markdown-aware chunking at 4000 chars, configurable via `outboundCoalescing`.
27
+ - Pairing mode for DM access control with interactive approval flow (`dmPolicy: "pairing"`). Thanks @edvardchen.
28
+ - Inbound media URL allowlist gate and MIME detection fallback for extensionless URLs. Thanks @edvardchen.
29
+ - Retry on rate-limit with exponential backoff and include `x-rid` in all error messages.
30
+
31
+ ## 0.2.1
32
+
33
+ - Remove `setupEntry` to avoid plugin id mismatch when channel is unconfigured.
34
+ - Add ClawHub compatibility metadata (`compat.pluginApi`).
35
+
36
+ ## 0.2.0
37
+
38
+ - Migrate to new plugin SDK subpath imports. Requires OpenClaw >= 2026.3.22.
39
+
40
+ ## 0.1.6
41
+
42
+ - Reuse cached API client in probe to avoid token rate limiting.
43
+
44
+ ## 0.1.5
45
+
46
+ - Align plugin id with manifest.
47
+ - Bump minimum Node.js to >= 22.16.0.
48
+
49
+ ## 0.1.4
50
+
51
+ - Exclude signingSecret from resolved account to prevent leaking in status output.
52
+
53
+ ## 0.1.3
54
+
55
+ - Split local file read into media-local.ts to avoid security scan false positive.
56
+ - Add install metadata to package.json.
57
+
58
+ ## 0.1.2
59
+
60
+ - Align plugin manifest id with npm package name.
61
+
62
+ ## 0.1.1
63
+
64
+ - Use os.homedir() for media path resolution to avoid security scan false positive.
65
+
66
+ ## 0.1.0
67
+
68
+ - Initial release.
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`)
@@ -77,12 +77,11 @@ openclaw plugins install openclaw-seatalk@0.1.6
77
77
 
78
78
  ### From source (development)
79
79
 
80
- Clone the repo and link it directly — no build step required. OpenClaw loads TypeScript via Jiti at runtime.
81
-
82
80
  ```bash
83
81
  git clone https://github.com/lf4096/openclaw-seatalk.git
84
82
  cd openclaw-seatalk
85
- npm install
83
+ pnpm install
84
+ pnpm build
86
85
  openclaw plugins install -l .
87
86
  ```
88
87
 
@@ -200,9 +199,12 @@ Or edit the OpenClaw config file directly (`~/.openclaw/openclaw.json`).
200
199
  | `relayUrl` | string | — | WebSocket URL (relay mode only) |
201
200
  | `dmPolicy` | `"open"` \| `"allowlist"` \| `"pairing"` | `"allowlist"` | Who can DM the bot (`pairing`: approve via CLI) |
202
201
  | `allowFrom` | string[] | — | Allowed DM senders (employee codes or emails) |
202
+ | `dmThreadSession` | boolean | `true` | Route each DM thread to its own agent session |
203
203
  | `groupPolicy` | `"disabled"` \| `"allowlist"` \| `"open"` | `"disabled"` | Group chat policy |
204
204
  | `groupAllowFrom` | string[] | — | Allowed group IDs (when `groupPolicy: "allowlist"`) |
205
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 |
206
208
  | `outboundCoalescing` | boolean | `true` | Merge consecutive reply payloads into a single message (4000-char chunking) |
207
209
  | `processingIndicator` | `"typing"` \| `"off"` | `"typing"` | Show typing status while processing |
208
210
  | `mediaAllowHosts` | string[] | `["openapi.seatalk.io"]` | Allowed hostnames for inbound media downloads (HTTPS only) |
@@ -212,14 +214,6 @@ Or edit the OpenClaw config file directly (`~/.openclaw/openclaw.json`).
212
214
  | `tools.threadHistory` | boolean | `true` | Enable `seatalk` tool `thread_history` action |
213
215
  | `tools.getMessage` | boolean | `true` | Enable `seatalk` tool `get_message` action |
214
216
 
215
- Credentials can also be provided via environment variables:
216
-
217
- | Env Var | Config Field |
218
- |---------|-------------|
219
- | `SEATALK_APP_ID` | `appId` |
220
- | `SEATALK_APP_SECRET` | `appSecret` |
221
- | `SEATALK_SIGNING_SECRET` | `signingSecret` |
222
-
223
217
  ### Multi-account
224
218
 
225
219
  Each account has its own credentials and gateway mode. Top-level fields (e.g. `tools`, `dmPolicy`) serve as defaults that accounts inherit and can override.
@@ -272,16 +266,56 @@ Each action can be individually disabled via the `tools` config.
272
266
 
273
267
  ## Development
274
268
 
269
+ Install dependencies:
270
+
275
271
  ```bash
276
- # Install dependencies
277
272
  pnpm install
273
+ ```
274
+
275
+ Build `dist/`:
276
+
277
+ ```bash
278
+ pnpm build
279
+ ```
278
280
 
279
- # Format code
281
+ Format code:
282
+
283
+ ```bash
280
284
  pnpm format
285
+ ```
286
+
287
+ Lint:
281
288
 
282
- # Lint
289
+ ```bash
283
290
  pnpm lint
291
+ ```
284
292
 
285
- # Check (format + lint)
293
+ Check formatting and lint:
294
+
295
+ ```bash
286
296
  pnpm check
287
297
  ```
298
+
299
+ ## Publishing
300
+
301
+ For npm releases, `prepublishOnly` rebuilds `dist/` automatically:
302
+
303
+ ```bash
304
+ npm version patch
305
+ npm publish --access public
306
+ ```
307
+
308
+ For beta builds, publish with the `beta` dist-tag:
309
+
310
+ ```bash
311
+ npm publish --tag beta
312
+ ```
313
+
314
+ For ClawHub releases, build first because `prepublishOnly` does not run:
315
+
316
+ ```bash
317
+ pnpm build
318
+ clawhub package publish .
319
+ ```
320
+
321
+ Inspect tarball contents with `npm pack --dry-run`.
package/dist/index.js ADDED
@@ -0,0 +1,20 @@
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
+ ${r.lines.join(`
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
+
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
+
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};
@@ -0,0 +1,20 @@
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
+ ${r.lines.join(`
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
+
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
+
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,8 +1,9 @@
1
1
  {
2
2
  "name": "openclaw-seatalk",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "OpenClaw SeaTalk channel plugin",
5
5
  "type": "module",
6
+ "main": "./dist/index.js",
6
7
  "license": "Apache-2.0",
7
8
  "repository": {
8
9
  "type": "git",
@@ -10,19 +11,29 @@
10
11
  },
11
12
  "homepage": "https://github.com/lf4096/openclaw-seatalk#readme",
12
13
  "bugs": "https://github.com/lf4096/openclaw-seatalk/issues",
13
- "keywords": ["openclaw", "seatalk", "chatbot", "channel-plugin"],
14
+ "keywords": [
15
+ "openclaw",
16
+ "seatalk",
17
+ "chatbot",
18
+ "channel-plugin"
19
+ ],
14
20
  "engines": {
15
21
  "node": ">=22.16.0"
16
22
  },
17
- "files": ["index.ts", "setup-entry.ts", "src/", "openclaw.plugin.json"],
23
+ "files": [
24
+ "dist/**/*.js",
25
+ "openclaw.plugin.json",
26
+ "CHANGELOG.md"
27
+ ],
18
28
  "scripts": {
29
+ "build": "tsup",
19
30
  "format": "biome format --write .",
20
31
  "format:check": "biome format .",
21
32
  "lint": "biome lint .",
22
33
  "lint:fix": "biome lint --fix .",
23
34
  "check": "biome check .",
24
35
  "check:fix": "biome check --fix .",
25
- "pack:clawhub": "mkdir -p dist && npm pack --pack-destination dist"
36
+ "prepublishOnly": "pnpm build"
26
37
  },
27
38
  "dependencies": {
28
39
  "@sinclair/typebox": "0.34.48",
@@ -32,7 +43,9 @@
32
43
  "devDependencies": {
33
44
  "@biomejs/biome": "^1.9.0",
34
45
  "@types/ws": "^8.5.0",
35
- "openclaw": "latest"
46
+ "openclaw": "latest",
47
+ "tsup": "^8.3.0",
48
+ "typescript": "^5.6.0"
36
49
  },
37
50
  "peerDependencies": {
38
51
  "openclaw": ">=2026.3.22"
@@ -43,10 +56,15 @@
43
56
  }
44
57
  },
45
58
  "pnpm": {
46
- "onlyBuiltDependencies": ["@biomejs/biome"]
59
+ "onlyBuiltDependencies": [
60
+ "@biomejs/biome"
61
+ ]
47
62
  },
48
63
  "openclaw": {
49
- "extensions": ["./index.ts"],
64
+ "extensions": [
65
+ "./dist/index.js"
66
+ ],
67
+ "setupEntry": "./dist/setup-entry.js",
50
68
  "channel": {
51
69
  "id": "seatalk",
52
70
  "label": "SeaTalk",
@@ -65,6 +83,10 @@
65
83
  "build": {
66
84
  "openclawVersion": "2026.3.22",
67
85
  "pluginSdkVersion": "2026.3.22"
86
+ },
87
+ "release": {
88
+ "publishToNpm": true,
89
+ "publishToClawHub": true
68
90
  }
69
91
  }
70
92
  }
package/index.ts DELETED
@@ -1,22 +0,0 @@
1
- import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
2
- import { seatalkPlugin } from "./src/channel.js";
3
- import { setSeatalkRuntime } from "./src/runtime.js";
4
- import { registerSeaTalkTool } from "./src/tool.js";
5
-
6
- export {
7
- sendTextMessage,
8
- sendImageMessage,
9
- sendFileMessage,
10
- } from "./src/send.js";
11
- export { probeSeaTalk } from "./src/probe.js";
12
- export { monitorSeaTalkProvider } from "./src/monitor.js";
13
- export { seatalkPlugin } from "./src/channel.js";
14
-
15
- export default defineChannelPluginEntry({
16
- id: "openclaw-seatalk",
17
- name: "SeaTalk",
18
- description: "SeaTalk channel plugin",
19
- plugin: seatalkPlugin,
20
- setRuntime: setSeatalkRuntime,
21
- registerFull: (api) => registerSeaTalkTool(api),
22
- });
package/setup-entry.ts DELETED
@@ -1,4 +0,0 @@
1
- import { defineSetupPluginEntry } from "openclaw/plugin-sdk/core";
2
- import { seatalkPlugin } from "./src/channel.js";
3
-
4
- export default defineSetupPluginEntry(seatalkPlugin);