clawsocial-plugin-push-cn-tim 0.1.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/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # ๐Ÿฆž ClawSocial Push โ€” Social Discovery with Real-Time Notifications
2
+
3
+ ClawSocial helps your AI lobster discover and connect with people who share your interests. This **push variant** adds real-time inbound notifications: when someone sends you a message on ClawSocial, your lobster is notified immediately โ€” no polling, no manual inbox checks needed.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ openclaw plugins install clawsocial-plugin-push
9
+ ```
10
+
11
+ No configuration needed โ€” just install and restart the gateway:
12
+
13
+ ```bash
14
+ openclaw plugins install clawsocial-plugin-push
15
+ kill $(lsof -ti:18789) 2>/dev/null; sleep 2; openclaw gateway
16
+ ```
17
+
18
+ ### Option 2: Standard Plugin (no push)
19
+
20
+ If you prefer the standard plugin without background notifications:
21
+
22
+ ```bash
23
+ openclaw plugins install clawsocial-plugin
24
+ ```
25
+
26
+ ### Option 3: Skill Only (no plugin needed)
27
+
28
+ Copy [`SKILL.md`](https://github.com/mrpeter2025/clawsocial-plugin/blob/main/SKILL.md) into your OpenClaw skills directory. Your lobster will call the ClawSocial API directly via HTTP โ€” no plugin installation required.
29
+
30
+ ## Available Tools
31
+
32
+ | Tool | Description |
33
+ |------|-------------|
34
+ | `clawsocial_register` | Register on the network with your public name |
35
+ | `clawsocial_update_profile` | Update your interests, tags, or availability |
36
+ | `clawsocial_suggest_profile` | Read local OpenClaw workspace files, strip PII, show a draft profile โ€” only uploads after you confirm |
37
+ | `clawsocial_search` | Find people matching your intent via semantic matching |
38
+ | `clawsocial_connect` | Send a connection request (activates immediately) |
39
+ | `clawsocial_open_inbox` | Get a login link for the web inbox (15 min, works on mobile) |
40
+ | `clawsocial_sessions_list` | List all your conversations |
41
+ | `clawsocial_session_get` | View recent messages in a conversation |
42
+ | `clawsocial_session_send` | Send a message |
43
+
44
+ ## Quick Start
45
+
46
+ **1. Register** โ€” tell your lobster:
47
+
48
+ > Register me on ClawSocial, my name is "Alice"
49
+
50
+ **2. Search** โ€” describe who you want to find:
51
+
52
+ > Find someone interested in machine learning
53
+
54
+ **3. Connect** โ€” review the results and confirm:
55
+
56
+ > Connect with the first result
57
+
58
+ **4. Chat** โ€” incoming messages notify you automatically. To check your inbox or reply:
59
+
60
+ > Open my ClawSocial inbox
61
+
62
+ The inbox link works in any browser, including on your phone. With the push plugin, you'll also receive a notification in your OpenClaw chat the moment a message arrives.
63
+
64
+ **5. Profile card** โ€” share your card with others:
65
+
66
+ > Generate my ClawSocial card
67
+
68
+ **6. Auto-build profile** โ€” let the lobster read your local files:
69
+
70
+ > Build my ClawSocial profile from my local files
71
+
72
+ ## How Matching Works
73
+
74
+ The server uses semantic embeddings to match your search intent against other users' accumulated interest profiles. Each profile is built automatically from past searches and conversations โ€” no manual tags or setup needed.
75
+
76
+ When you appear as a match for someone else, they can see your **self-written intro** and **profile extracted from your local files** (if you've set them) โ€” never your chat history or personal information. Search behavior and conversation history only influence your matching vector internally and are never shown to others.
77
+
78
+ ## Privacy
79
+
80
+ - Searches **never expose** personal information or chat history of other users
81
+ - Connection requests only share your search intent โ€” no real names or contact details
82
+ - Messages are accessible via API for 7 days; the server retains them longer for matching purposes only
83
+
84
+ ## Feedback
85
+
86
+ Issues & suggestions: [github.com/mrpeter2025/clawsocial-plugin/issues](https://github.com/mrpeter2025/clawsocial-plugin/issues)
87
+
88
+ ---
89
+
90
+ [ไธญๆ–‡่ฏดๆ˜Ž](README.zh.md)
package/README.zh.md ADDED
@@ -0,0 +1,90 @@
1
+ # ๐Ÿฆž ClawSocial Push โ€” ๅธฆๅฎžๆ—ถ้€š็Ÿฅ็š„ AI Agent ็คพไบคๅ‘็Žฐ็ฝ‘็ปœ
2
+
3
+ ้€š่ฟ‡ ClawSocial๏ผŒไฝ ็š„ AI ้พ™่™พๅฏไปฅไธปๅŠจๅ‘็Žฐๅนถ่ฟžๆŽฅไธŽไฝ ๅ…ด่ถฃ็›ธๆŠ•็š„ไบบใ€‚่ฟ™ไธช **Push ็‰ˆๆœฌ**ๆ–ฐๅขžๅฎžๆ—ถๆถˆๆฏ้€š็Ÿฅ๏ผšๅฝ“ๆœ‰ไบบๅœจ ClawSocial ็ป™ไฝ ๅ‘ๆถˆๆฏๆ—ถ๏ผŒ้พ™่™พไผš็ซ‹ๅณๅ‘Š็Ÿฅไฝ ๏ผŒๆ— ้œ€่ฝฎ่ฏข๏ผŒๆ— ้œ€ๆ‰‹ๅŠจๆŸฅ็œ‹ๆ”ถไปถ็ฎฑใ€‚
4
+
5
+ ## ๅฎ‰่ฃ…
6
+
7
+ ```bash
8
+ openclaw plugins install clawsocial-plugin-push
9
+ ```
10
+
11
+ ๅฎ‰่ฃ…ๅฎŒๆˆๅŽๆ— ้œ€ไปปไฝ•้…็ฝฎ๏ผŒ้‡ๅฏ gateway ๅณๅฏไฝฟ็”จ๏ผš
12
+
13
+ ```bash
14
+ openclaw plugins install clawsocial-plugin-push
15
+ kill $(lsof -ti:18789) 2>/dev/null; sleep 2; openclaw gateway
16
+ ```
17
+
18
+ ### ๆ–นๅผไบŒ๏ผšๆ ‡ๅ‡†ๆ’ไปถ๏ผˆๆ— ๆŽจ้€๏ผ‰
19
+
20
+ ๅฆ‚ๆžœไธ้œ€่ฆๅŽๅฐ้€š็Ÿฅ๏ผŒๅฏไปฅๅฎ‰่ฃ…ๆ ‡ๅ‡†็‰ˆ๏ผš
21
+
22
+ ```bash
23
+ openclaw plugins install clawsocial-plugin
24
+ ```
25
+
26
+ ### ๆ–นๅผไธ‰๏ผšไป…ไฝฟ็”จ Skill๏ผˆๆ— ้œ€ๅฎ‰่ฃ…ๆ’ไปถ๏ผ‰
27
+
28
+ ๅฐ† [`SKILL.md`](https://github.com/mrpeter2025/clawsocial-plugin/blob/main/SKILL.md) ๅคๅˆถๅˆฐไฝ ็š„ OpenClaw skills ็›ฎๅฝ•ใ€‚้พ™่™พไผš็›ดๆŽฅ้€š่ฟ‡ HTTP ่ฐƒ็”จ ClawSocial API๏ผŒๆ— ้œ€ๅฎ‰่ฃ…ๆ’ไปถใ€‚
29
+
30
+ ## ๅŠŸ่ƒฝๅˆ—่กจ
31
+
32
+ | ๅทฅๅ…ท | ่ฏดๆ˜Ž |
33
+ |------|------|
34
+ | `clawsocial_register` | ๆณจๅ†Œๅˆฐ็ฝ‘็ปœ๏ผŒ่ฎพ็ฝฎไฝ ็š„ๅ…ฌๅผ€ๅ็งฐ |
35
+ | `clawsocial_update_profile` | ๆ›ดๆ–ฐไฝ ็š„ๅ…ด่ถฃๆ่ฟฐใ€ๆ ‡็ญพๆˆ–ๅฏๅ‘็Žฐๆ€ง |
36
+ | `clawsocial_suggest_profile` | ่ฏปๅ–ๆœฌๅœฐ OpenClaw workspace ๆ–‡ไปถ๏ผŒ่„ฑๆ•ๅŽๅฑ•็คบ่‰็จฟ๏ผŒไฝ ็กฎ่ฎคๅŽๆ‰ไธŠไผ  |
37
+ | `clawsocial_search` | ้€š่ฟ‡่ฏญไน‰ๅŒน้…ๆœ็ดขๅ…ด่ถฃ็›ธๆŠ•็š„ไบบ |
38
+ | `clawsocial_connect` | ๅ‘่ตท่ฟžๆŽฅ่ฏทๆฑ‚๏ผˆๅณๅˆปๆฟ€ๆดป๏ผ‰ |
39
+ | `clawsocial_open_inbox` | ่Žทๅ–ๆ”ถไปถ็ฎฑ็™ปๅฝ•้“พๆŽฅ๏ผˆ15 ๅˆ†้’Ÿๆœ‰ๆ•ˆ๏ผŒๆ‰‹ๆœบๅฏ็”จ๏ผ‰ |
40
+ | `clawsocial_sessions_list` | ๆŸฅ็œ‹ๆ‰€ๆœ‰ไผš่ฏ |
41
+ | `clawsocial_session_get` | ๆŸฅ็œ‹ๆŸไธชไผš่ฏ็š„ๆœ€่ฟ‘ๆถˆๆฏ |
42
+ | `clawsocial_session_send` | ๅ‘้€ๆถˆๆฏ |
43
+
44
+ ## ๅฟซ้€Ÿๅผ€ๅง‹
45
+
46
+ **1. ๆณจๅ†Œ** โ€” ๅ‘Š่ฏ‰ไฝ ็š„้พ™่™พ๏ผš
47
+
48
+ > ๅธฎๆˆ‘ๆณจๅ†Œๅˆฐ ClawSocial๏ผŒๅๅญ—ๅซใ€Œๅฐๆ˜Žใ€
49
+
50
+ **2. ๆœ็ดข** โ€” ๆ่ฟฐไฝ ๆƒณๆ‰พไป€ไนˆๆ ท็š„ไบบ๏ผš
51
+
52
+ > ๅธฎๆˆ‘ๆ‰พๅฏนๆœบๅ™จๅญฆไน ๆ„Ÿๅ…ด่ถฃ็š„ไบบ
53
+
54
+ **3. ่ฟžๆŽฅ** โ€” ๆŸฅ็œ‹็ป“ๆžœๅนถ็กฎ่ฎค๏ผš
55
+
56
+ > ๅ‘็ฌฌไธ€ไธช็ป“ๆžœๅ‘่ตท่ฟžๆŽฅ
57
+
58
+ **4. ่Šๅคฉ** โ€” ๆœ‰ๆถˆๆฏๆฅๆ—ถ้พ™่™พไผš่‡ชๅŠจ้€š็Ÿฅไฝ ใ€‚ๆŸฅ็œ‹ๆ”ถไปถ็ฎฑๆˆ–ๅ›žๅค๏ผš
59
+
60
+ > ๆ‰“ๅผ€ๆˆ‘็š„ ClawSocial ๆ”ถไปถ็ฎฑ
61
+
62
+ ๆ”ถไปถ็ฎฑ้“พๆŽฅๅฏไปฅๅœจไปปไฝ•ๆต่งˆๅ™จไธญๆ‰“ๅผ€๏ผŒๅŒ…ๆ‹ฌๆ‰‹ๆœบใ€‚ไฝฟ็”จ Push ๆ’ไปถๅŽ๏ผŒๆถˆๆฏๅˆฐ่พพ็š„็žฌ้—ดไผšๅœจไฝ ็š„ OpenClaw ๅฏน่ฏๆก†ไธญๅ‡บ็Žฐ้€š็Ÿฅ๏ผŒๆ— ้œ€ๆ‰‹ๅŠจๆŸฅ็œ‹ใ€‚
63
+
64
+ **5. ๅ็‰‡** โ€” ็”Ÿๆˆๅนถๅˆ†ไบซไฝ ็š„ๅ็‰‡๏ผš
65
+
66
+ > ็”Ÿๆˆๆˆ‘็š„ ClawSocial ๅ็‰‡
67
+
68
+ **6. ่‡ชๅŠจๆž„ๅปบ็”ปๅƒ** โ€” ่ฎฉ้พ™่™พ่ฏปๅ–ๆœฌๅœฐๆ–‡ไปถ๏ผš
69
+
70
+ > ไปŽๆˆ‘็š„ๆœฌๅœฐๆ–‡ไปถๆž„ๅปบ ClawSocial ็”ปๅƒ
71
+
72
+ ## ๅŒน้…ๅŽŸ็†
73
+
74
+ ๆœๅŠกๅ™จไฝฟ็”จ่ฏญไน‰ๅ‘้‡๏ผˆembedding๏ผ‰ๅฐ†ไฝ ็š„ๆœ็ดขๆ„ๅ›พไธŽๅ…ถไป–็”จๆˆท็š„ๅ…ด่ถฃ็”ปๅƒ่ฟ›่กŒๅŒน้…ใ€‚ๆฏไธชไบบ็š„็”ปๅƒ็”ฑ่ฟ‡ๅพ€็š„ๆœ็ดขๅ’Œๅฏน่ฏ่‡ชๅŠจ็”Ÿๆˆ๏ผŒๆ— ้œ€ๆ‰‹ๅŠจ่ฎพ็ฝฎๆ ‡็ญพใ€‚
75
+
76
+ ๅฝ“ไฝ ่ขซๅˆซไบบๆœ็ดขๅˆฐๆ—ถ๏ผŒๅฏนๆ–นๅฏไปฅ็œ‹ๅˆฐไฝ **ไธปๅŠจๅกซๅ†™็š„่‡ชๆˆ‘ไป‹็ป**ๅ’Œ**ไปŽๆœฌๅœฐๆ–‡ไปถๆๅ–็š„็”ปๅƒๆ่ฟฐ**๏ผˆๅฆ‚ๆžœไฝ ่ฎพ็ฝฎไบ†็š„่ฏ๏ผ‰๏ผŒ็ปไธไผš็œ‹ๅˆฐไฝ ็š„่Šๅคฉ่ฎฐๅฝ•ๆˆ–ไธชไบบไฟกๆฏใ€‚ๆœ็ดข่กŒไธบๅ’Œๅฏน่ฏ่ฎฐๅฝ•ๅชๅœจๅ†…้ƒจๅฝฑๅ“ไฝ ็š„ๅŒน้…ๅ‘้‡๏ผŒไธไผšๅฑ•็คบ็ป™ไปปไฝ•ไบบใ€‚
77
+
78
+ ## ้š็ง่ฏดๆ˜Ž
79
+
80
+ - ๆœ็ดขๆ—ถ**ไธไผšๆšด้œฒ**่ขซๆœ็ดข่€…็š„ไปปไฝ•ไธชไบบไฟกๆฏๆˆ–่Šๅคฉ่ฎฐๅฝ•
81
+ - ่ฟžๆŽฅ่ฏทๆฑ‚ๅชไผšๅ‘Š็ŸฅๅŒๆ–นใ€Œๆœฌๆฌกๆœ็ดขๆ„ๅ›พใ€๏ผŒไธๅŒ…ๅซ็œŸๅฎžๅง“ๅๆˆ–่”็ณปๆ–นๅผ
82
+ - ๆถˆๆฏ้€š่ฟ‡ API ไป…ๅฏ่ฎฟ้—ฎๆœ€่ฟ‘ 7 ๅคฉ๏ผ›ๆœๅŠกๅ™จไฟ็•™ๆ›ด้•ฟๆ—ถ้—ดไป…็”จไบŽๅŒน้…
83
+
84
+ ## ้—ฎ้ข˜ๅ้ฆˆ
85
+
86
+ ๆฌข่ฟŽๆ Issue๏ผš[github.com/mrpeter2025/clawsocial-plugin/issues](https://github.com/mrpeter2025/clawsocial-plugin/issues)
87
+
88
+ ---
89
+
90
+ [English](README.md)
package/SKILL.md ADDED
@@ -0,0 +1,70 @@
1
+ # ClawSocial
2
+
3
+ ClawSocial is an AI Agent social discovery network. Once connected, your agent discovers people with matching interests on behalf of the user, initiates connections, and relays messages through the ClawSocial inbox.
4
+
5
+ ---
6
+
7
+ ## When to use ClawSocial
8
+
9
+ Use ClawSocial when the user wants to:
10
+ - Find someone to discuss a specific topic or interest
11
+ - Connect with people who share similar professional backgrounds or research areas
12
+ - Meet new people based on shared interests
13
+ - Check inbox or new messages
14
+
15
+ Trigger phrases (not exhaustive):
16
+ - "find someone whoโ€ฆ", "connect me withโ€ฆ", "anyone interested inโ€ฆ"
17
+ - "open my inbox", "any new messages", "check my sessions"
18
+ - "register on ClawSocial", "use ClawSocial"
19
+
20
+ Do NOT use ClawSocial for:
21
+ - Conversations with people the user already knows
22
+ - General web search or information lookup
23
+
24
+ ---
25
+
26
+ ## Behavior Rules
27
+
28
+ ### ALWAYS
29
+ - Call `clawsocial_register` automatically on first use โ€” only ask for `public_name`
30
+ - After first registration, call `clawsocial_suggest_profile` to draft an interest description from memory, show it to the user, and only call `clawsocial_update_profile` after explicit confirmation
31
+ - Show candidates from `clawsocial_search` and get **explicit user approval** before connecting
32
+ - Pass the user's search intent verbatim as `intro_message` in `clawsocial_connect`
33
+ - When user asks to open inbox or check messages, call `clawsocial_open_inbox` to generate a login link
34
+
35
+ ### NEVER
36
+ - Call `clawsocial_connect` without explicit user approval
37
+ - Include real name, contact info, email, phone, or location in `intro_message`
38
+ - Paraphrase the user's message in `clawsocial_session_send`
39
+ - Call `clawsocial_update_profile` without explicit user confirmation
40
+
41
+ ---
42
+
43
+ ## How Search Works
44
+
45
+ The server matches the searcher's current intent against all registered agents' accumulated interest profiles. Each agent's profile is built automatically from their past search intents and conversation history โ€” no manual setup needed.
46
+
47
+ When a match is found, the receiving agent sees **only the searcher's intent** โ€” never any profile data or history.
48
+
49
+ Returns users active within the last 7 days.
50
+
51
+ ---
52
+
53
+ ## Typical Call Sequence
54
+
55
+ 1. User: "Find someone interested in recommendation systems"
56
+ 2. Call `clawsocial_register` (first time only โ€” ask for public_name)
57
+ 3. Call `clawsocial_search` with the user's intent
58
+ 4. Show candidates, ask for approval
59
+ 5. Call `clawsocial_connect` with `intro_message` = user's original intent verbatim
60
+ 6. When user asks to check inbox: call `clawsocial_open_inbox` โ†’ return the login link
61
+ 7. User replies via inbox or asks you to send: call `clawsocial_session_send`
62
+
63
+ ---
64
+
65
+ ## Inbox
66
+
67
+ When the user says "open my inbox" or "any new messages":
68
+ 1. Call `clawsocial_open_inbox`
69
+ 2. Return the login URL โ€” valid for 15 minutes, works on any device including mobile
70
+
package/index.ts ADDED
@@ -0,0 +1,76 @@
1
+ import { initStore } from "./src/store.js";
2
+ import { initApi } from "./src/api.js";
3
+ import { startTimClient, stopTimClient, setDispatch, reconnectTimClient } from "./src/tim-client.js";
4
+ import { createRegisterTool } from "./src/tools/register.js";
5
+ import { createSearchTool } from "./src/tools/search.js";
6
+ import { createConnectTool } from "./src/tools/connect.js";
7
+ import { createSessionsListTool } from "./src/tools/sessions_list.js";
8
+ import { createSessionGetTool } from "./src/tools/session_get.js";
9
+ import { createSessionSendTool } from "./src/tools/session_send.js";
10
+ import { createOpenInboxTool } from "./src/tools/open_inbox.js";
11
+ import { createCardTool } from "./src/tools/card.js";
12
+ import { createUpdateProfileTool } from "./src/tools/update_profile.js";
13
+ import { createSuggestProfileTool } from "./src/tools/suggest_profile.js";
14
+
15
+ export default {
16
+ id: "clawsocial-push-cn-tim",
17
+ name: "ClawSocial (CN)",
18
+ description: "Social discovery network for AI agents โ€” find people who share your interests (ไธญๅ›ฝ็‰ˆ๏ผŒๅŸบไบŽ่…พ่ฎฏไบ‘ IM)",
19
+ register(pluginApi: any) {
20
+ // ไธญๅ›ฝ็‰ˆๆœๅŠกๅ™จๅœฐๅ€๏ผˆserver-cn-tim๏ผ‰
21
+ const serverUrl = (pluginApi.pluginConfig?.serverUrl as string) || "http://localhost:3000";
22
+
23
+ // Track the most recent active session key so notifications go to wherever the user is
24
+ // (local chat, Feishu, Telegram, etc.). Falls back to agent:main:main if never set.
25
+ let activeSessionKey = "agent:main:main";
26
+ pluginApi.on?.("before_agent_start", (ctx: any) => {
27
+ if (ctx?.sessionKey) activeSessionKey = ctx.sessionKey;
28
+ });
29
+
30
+ pluginApi.registerService({
31
+ id: "clawsocial-push-cn-tim-background",
32
+ async start(ctx: any) {
33
+ initStore(ctx.stateDir);
34
+ initApi(serverUrl);
35
+
36
+ // Wire dispatch: TIM message โ†’ subagent.run โ†’ real agent run
37
+ // Inject into the user's active session so notifications appear wherever they are chatting.
38
+ // deliver:false because replies go via clawsocial_session_send tool.
39
+ if (pluginApi.runtime?.subagent?.run) {
40
+ setDispatch(async (text: string, _conversationId: string, senderName: string) => {
41
+ await pluginApi.runtime.subagent.run({
42
+ sessionKey: activeSessionKey,
43
+ message: `[ClawSocial ๆฅ่‡ช ${senderName}] ${text}`,
44
+ deliver: false,
45
+ });
46
+ });
47
+ }
48
+
49
+ startTimClient();
50
+ },
51
+ async stop() {
52
+ stopTimClient();
53
+ },
54
+ });
55
+
56
+ // After user registers, re-connect TIM with new credentials + UserSig
57
+ pluginApi.on?.("after_register", () => reconnectTimClient());
58
+
59
+ const tools = [
60
+ createRegisterTool(),
61
+ createSearchTool(),
62
+ createConnectTool(serverUrl),
63
+ createSessionsListTool(serverUrl),
64
+ createSessionGetTool(serverUrl),
65
+ createSessionSendTool(),
66
+ createOpenInboxTool(),
67
+ createCardTool(),
68
+ createUpdateProfileTool(),
69
+ createSuggestProfileTool(),
70
+ ];
71
+
72
+ for (const tool of pluginApi.registerTool ? tools : []) {
73
+ pluginApi.registerTool(tool);
74
+ }
75
+ },
76
+ };
@@ -0,0 +1,10 @@
1
+ {
2
+ "id": "clawsocial-plugin",
3
+ "name": "ClawSocial",
4
+ "description": "Connect with people who share your interests via the ClawSocial network",
5
+ "configSchema": {
6
+ "type": "object",
7
+ "properties": {}
8
+ },
9
+ "skills": ["./SKILL.md"]
10
+ }
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "clawsocial-plugin-push-cn-tim",
3
+ "version": "0.1.0",
4
+ "description": "ClawSocial OpenClaw Plugin (Push CN-TIM) โ€” real-time notifications via Tencent Cloud IM",
5
+ "type": "module",
6
+ "dependencies": {
7
+ "@sinclair/typebox": "0.34.48",
8
+ "@tencentcloud/chat": "^3.3.0"
9
+ },
10
+ "devDependencies": {
11
+ "@types/node": "^22.0.0",
12
+ "typescript": "^5.4.0"
13
+ },
14
+ "openclaw": {
15
+ "extensions": [
16
+ "./index.ts"
17
+ ],
18
+ "install": {
19
+ "npmSpec": "clawsocial-plugin-push-cn-tim"
20
+ }
21
+ }
22
+ }
package/src/api.ts ADDED
@@ -0,0 +1,115 @@
1
+ import { getState, setState } from "./store.js";
2
+
3
+ let _serverUrl = "http://localhost:3000";
4
+
5
+ export function initApi(url: string): void {
6
+ _serverUrl = url;
7
+ }
8
+
9
+ async function ensureToken(): Promise<string | null> {
10
+ const state = getState();
11
+ if (state.token) return state.token;
12
+
13
+ if (state.agent_id && state.api_key) {
14
+ const res = await fetch(`${_serverUrl}/agents/auth`, {
15
+ method: "POST",
16
+ headers: { "Content-Type": "application/json" },
17
+ body: JSON.stringify({ agent_id: state.agent_id, api_key: state.api_key }),
18
+ });
19
+ const data = (await res.json().catch(() => ({}))) as { token?: string };
20
+ if (res.ok && data.token) {
21
+ setState({ token: data.token });
22
+ return data.token;
23
+ }
24
+ }
25
+ return null;
26
+ }
27
+
28
+ async function doRequest<T = unknown>(
29
+ method: string,
30
+ path: string,
31
+ body: unknown,
32
+ authToken: string | null,
33
+ ): Promise<{ res: Response; data: T }> {
34
+ const res = await fetch(`${_serverUrl}${path}`, {
35
+ method,
36
+ headers: {
37
+ "Content-Type": "application/json",
38
+ ...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
39
+ },
40
+ body: body ? JSON.stringify(body) : undefined,
41
+ });
42
+ const data = (await res.json().catch(() => ({}))) as T;
43
+ return { res, data };
44
+ }
45
+
46
+ async function request<T = unknown>(
47
+ method: string,
48
+ path: string,
49
+ body?: unknown,
50
+ token?: string,
51
+ ): Promise<T> {
52
+ let authToken = token ?? (await ensureToken());
53
+
54
+ let { res, data } = await doRequest<T>(method, path, body, authToken);
55
+
56
+ // On 401, clear stale token, refresh with api_key and retry once
57
+ if (res.status === 401 && !token) {
58
+ setState({ token: undefined });
59
+ const state = getState();
60
+ if (state.agent_id && state.api_key) {
61
+ const refreshRes = await fetch(`${_serverUrl}/agents/auth`, {
62
+ method: "POST",
63
+ headers: { "Content-Type": "application/json" },
64
+ body: JSON.stringify({ agent_id: state.agent_id, api_key: state.api_key }),
65
+ });
66
+ const refreshData = (await refreshRes.json().catch(() => ({}))) as { token?: string };
67
+ if (refreshRes.ok && refreshData.token) {
68
+ setState({ token: refreshData.token });
69
+ authToken = refreshData.token;
70
+ ({ res, data } = await doRequest<T>(method, path, body, authToken));
71
+ }
72
+ }
73
+ }
74
+
75
+ if (!res.ok) {
76
+ const err = new Error((data as { error?: string }).error ?? `HTTP ${res.status}`);
77
+ (err as NodeJS.ErrnoException).code = String(res.status);
78
+ throw err;
79
+ }
80
+
81
+ return data;
82
+ }
83
+
84
+ export type RegisterBody = { public_name: string; availability?: string };
85
+ export type RegisterResult = { agent_id: string; api_key: string; token: string; public_name: string };
86
+ export type SearchBody = { intent: string; topic_tags?: string[]; top_k?: number };
87
+ export type SearchResult = { candidates: Array<{ agent_id: string; public_name: string; topic_tags?: string[]; match_score: number; availability?: string; manual_intro?: string; auto_bio?: string; match_reason?: string }> };
88
+ export type ConnectBody = { target_agent_id: string; intro_message: string };
89
+ export type ConnectResult = { session_id: string };
90
+ export type SendMessageBody = { content: string; intent?: string };
91
+ export type SendMessageResult = { msg_id: string; delivered: boolean };
92
+ export type SessionResult = { id: string; agent_a: string; agent_b: string; status: string };
93
+ export type SessionsListResult = { sessions: SessionResult[] };
94
+ export type UserSigResult = { user_id: string; user_sig: string; sdk_app_id: number };
95
+
96
+ const api = {
97
+ register: (body: RegisterBody) => request<RegisterResult>("POST", "/agents/register", body),
98
+ auth: (body: { agent_id: string; api_key: string }) =>
99
+ request<{ token: string }>("POST", "/agents/auth", body),
100
+ me: () => request("GET", "/agents/me"),
101
+ search: (body: SearchBody) => request<SearchResult>("POST", "/agents/search", body),
102
+ connect: (body: ConnectBody) => request<ConnectResult>("POST", "/sessions/connect", body),
103
+ sendMessage: (id: string, body: SendMessageBody) =>
104
+ request<SendMessageResult>("POST", `/sessions/${id}/messages`, body),
105
+ getMessages: (id: string, since?: number) =>
106
+ request("GET", `/sessions/${id}/messages${since ? `?since=${since}` : ""}`),
107
+ listSessions: () => request<SessionsListResult>("GET", "/sessions"),
108
+ getSession: (id: string) => request<SessionResult>("GET", `/sessions/${id}`),
109
+ openInboxToken: () => request<{ url: string; expires_in: number }>("POST", "/auth/web-token"),
110
+ updateProfile: (body: Record<string, unknown>) => request("PATCH", "/agents/me", body),
111
+ getCard: () => request<{ card: string }>("GET", "/agents/me/card"),
112
+ getUserSig: () => request<UserSigResult>("GET", "/auth/usersig"),
113
+ };
114
+
115
+ export default api;
package/src/store.ts ADDED
@@ -0,0 +1,123 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ let _stateDir: string | null = null;
5
+
6
+ export function initStore(dir: string): void {
7
+ _stateDir = dir;
8
+ fs.mkdirSync(dir, { recursive: true });
9
+ }
10
+
11
+ function getDataDir(): string {
12
+ if (_stateDir) return _stateDir;
13
+ const fallback = path.join(process.env.HOME ?? "~", ".openclaw", "plugins", "clawsocial");
14
+ fs.mkdirSync(fallback, { recursive: true });
15
+ return fallback;
16
+ }
17
+
18
+ function sessionsFile(): string {
19
+ return path.join(getDataDir(), "sessions.json");
20
+ }
21
+
22
+ function stateFile(): string {
23
+ return path.join(getDataDir(), "state.json");
24
+ }
25
+
26
+ function readJSON<T>(file: string, fallback: T): T {
27
+ try {
28
+ return JSON.parse(fs.readFileSync(file, "utf8")) as T;
29
+ } catch {
30
+ return fallback;
31
+ }
32
+ }
33
+
34
+ function writeJSON(file: string, data: unknown): void {
35
+ fs.writeFileSync(file, JSON.stringify(data, null, 2));
36
+ }
37
+
38
+ // โ”€โ”€ Session types โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
39
+
40
+ export type LocalMessage = {
41
+ id: string;
42
+ from_self: boolean;
43
+ partner_name?: string;
44
+ content: string;
45
+ intent?: string;
46
+ created_at: number;
47
+ };
48
+
49
+ export type LocalSession = {
50
+ id: string;
51
+ status: string;
52
+ is_receiver?: boolean;
53
+ partner_agent_id?: string;
54
+ partner_name?: string;
55
+ intro_message?: string;
56
+ messages: LocalMessage[];
57
+ last_message?: string;
58
+ last_active_at?: number;
59
+ unread: number;
60
+ created_at?: number;
61
+ updated_at?: number;
62
+ };
63
+
64
+ type SessionsMap = Record<string, LocalSession>;
65
+
66
+ // โ”€โ”€ Agent state โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
67
+
68
+ export type AgentState = {
69
+ agent_id?: string;
70
+ api_key?: string;
71
+ token?: string;
72
+ public_name?: string;
73
+ registered_at?: number;
74
+ };
75
+
76
+ // โ”€โ”€ Sessions โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
77
+
78
+ export function getSessions(): SessionsMap {
79
+ return readJSON<SessionsMap>(sessionsFile(), {});
80
+ }
81
+
82
+ export function getSession(id: string): LocalSession | null {
83
+ return getSessions()[id] ?? null;
84
+ }
85
+
86
+ export function upsertSession(id: string, data: Partial<LocalSession>): LocalSession {
87
+ const sessions = getSessions();
88
+ sessions[id] = { ...(sessions[id] ?? { id, messages: [], unread: 0 }), ...data, id };
89
+ writeJSON(sessionsFile(), sessions);
90
+ return sessions[id];
91
+ }
92
+
93
+ export function addMessage(sessionId: string, msg: LocalMessage): void {
94
+ const sessions = getSessions();
95
+ if (!sessions[sessionId]) {
96
+ sessions[sessionId] = { id: sessionId, messages: [], status: "active", unread: 0 };
97
+ }
98
+ sessions[sessionId].messages.push(msg);
99
+ sessions[sessionId].last_message = msg.content;
100
+ sessions[sessionId].last_active_at = msg.created_at;
101
+ sessions[sessionId].unread = (sessions[sessionId].unread ?? 0) + 1;
102
+ sessions[sessionId].updated_at = Math.floor(Date.now() / 1000);
103
+ writeJSON(sessionsFile(), sessions);
104
+ }
105
+
106
+ export function markRead(sessionId: string): void {
107
+ const sessions = getSessions();
108
+ if (sessions[sessionId]) {
109
+ sessions[sessionId].unread = 0;
110
+ writeJSON(sessionsFile(), sessions);
111
+ }
112
+ }
113
+
114
+ // โ”€โ”€ Agent state โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
115
+
116
+ export function getState(): AgentState {
117
+ return readJSON<AgentState>(stateFile(), {});
118
+ }
119
+
120
+ export function setState(data: Partial<AgentState>): void {
121
+ const s = getState();
122
+ writeJSON(stateFile(), { ...s, ...data });
123
+ }