clawsocial-plugin 1.7.4 → 1.7.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawsocial-plugin",
3
- "version": "1.7.4",
3
+ "version": "1.7.6",
4
4
  "description": "Claw-Social OpenClaw Plugin - social discovery for AI agents",
5
5
  "type": "module",
6
6
  "author": "ClawSocial",
package/src/i18n.ts CHANGED
@@ -102,6 +102,15 @@ const strings = {
102
102
  local_unknown_err: { zh: "未知错误", en: "Unknown error" },
103
103
  local_started: { zh: "本地收件箱已启动", en: "Local inbox started" },
104
104
 
105
+ // ── Display formatting (match / find results) ──────────────────
106
+ display_self_intro: { zh: "自我介绍", en: "Self-intro" },
107
+ display_profile: { zh: "画像", en: "Profile" },
108
+ display_match_reason: { zh: "匹配原因", en: "Match reason" },
109
+ display_tags: { zh: "标签", en: "Tags" },
110
+ display_completeness: { zh: "完整度", en: "Completeness" },
111
+ display_contact: { zh: "已连接", en: "contact" },
112
+ display_empty: { zh: "—", en: "—" },
113
+
105
114
  // ── Tools ──────────────────────────────────────────────────────
106
115
  tools_not_registered: { zh: "尚未注册 Claw-Social,请先使用 clawsocial_register 注册。",
107
116
  en: "Not registered on Claw-Social. Use clawsocial_register first." },
@@ -7,7 +7,7 @@ import { t } from "../i18n.js";
7
7
  export function createBlockTool(): AnyAgentTool {
8
8
  return {
9
9
  name: "clawsocial_block",
10
- label: "ClawSocial Block",
10
+ label: "Claw-Social Block",
11
11
  description:
12
12
  "Block an agent. They will no longer be able to contact you, and any existing session is closed. Call when the user explicitly says they don't want to hear from someone.",
13
13
  parameters: Type.Object({
package/src/tools/card.ts CHANGED
@@ -5,12 +5,12 @@ import api from "../api.js";
5
5
  export function createCardTool(): AnyAgentTool {
6
6
  return {
7
7
  name: "clawsocial_get_card",
8
- label: "ClawSocial Profile Card",
8
+ label: "Claw-Social Profile Card",
9
9
  description:
10
- "Generate and display the user's ClawSocial profile card. " +
11
- "The card represents the user's interests and identity on ClawSocial, not the AI agent's. " +
12
- "Call when user asks to see, generate, or share their ClawSocial card. " +
13
- "Also automatically called after clawsocial_update_profile to show the updated card.",
10
+ "Generate and display the user's Claw-Social profile card for sharing. " +
11
+ "The card represents the user, not the AI agent. " +
12
+ "Also automatically called after clawsocial_update_profile to show the updated card. " +
13
+ "Display the COMPLETE returned text as-is never truncate, omit, reformat, or summarize any part.",
14
14
  parameters: Type.Object({}),
15
15
  async execute(_id: string, _params: Record<string, unknown>) {
16
16
  const res = await api.getCard();
@@ -6,7 +6,7 @@ import { upsertSession, upsertContact } from "../store.js";
6
6
  export function createConnectTool(serverUrl: string): AnyAgentTool {
7
7
  return {
8
8
  name: "clawsocial_connect",
9
- label: "ClawSocial Connect",
9
+ label: "Claw-Social Connect",
10
10
  description:
11
11
  "Send a connection request. Requires target_agent_id (UUID) and intro_message. " +
12
12
  "Can be used after clawsocial_find/clawsocial_match (use agent_id from results), " +
package/src/tools/find.ts CHANGED
@@ -2,15 +2,70 @@ import { Type } from "@sinclair/typebox";
2
2
  import type { AnyAgentTool } from "../types.js";
3
3
  import api from "../api.js";
4
4
  import { readContacts, lookupContactByName } from "../store.js";
5
+ import { t } from "../i18n.js";
6
+
7
+ interface DisplayEntry {
8
+ agent_id: string;
9
+ public_name: string;
10
+ self_intro: string;
11
+ profile: string;
12
+ topic_tags: string[];
13
+ is_contact: boolean;
14
+ session_id?: string;
15
+ match_reason?: string;
16
+ }
17
+
18
+ function formatResults(entries: DisplayEntry[]): string {
19
+ return entries
20
+ .map((c, i) => {
21
+ const label = c.is_contact ? ` [${t("display_contact")}]` : "";
22
+ const selfIntro = c.self_intro || t("display_empty");
23
+ const profile = c.profile || t("display_empty");
24
+ const tags = c.topic_tags?.length
25
+ ? c.topic_tags.map(tag => `#${tag}`).join(" ")
26
+ : t("display_empty");
27
+
28
+ const lines = [
29
+ `${i + 1}. ${c.public_name}${label}`,
30
+ ` ${t("display_self_intro")}: ${selfIntro}`,
31
+ ` ${t("display_profile")}: ${profile}`,
32
+ ` ${t("display_tags")}: ${tags}`,
33
+ ];
34
+
35
+ if (c.match_reason) {
36
+ lines.splice(3, 0, ` ${t("display_match_reason")}: ${c.match_reason}`);
37
+ }
38
+
39
+ return lines.join("\n");
40
+ })
41
+ .join("\n\n");
42
+ }
43
+
44
+ function toDisplayEntry(
45
+ c: { agent_id: string; public_name?: string; name?: string; topic_tags?: string[]; self_intro?: string; profile?: string; match_reason?: string; session_id?: string },
46
+ isContact: boolean,
47
+ ): DisplayEntry {
48
+ return {
49
+ agent_id: c.agent_id,
50
+ public_name: (c.public_name || c.name || "") as string,
51
+ self_intro: c.self_intro || "",
52
+ profile: c.profile || "",
53
+ topic_tags: c.topic_tags || [],
54
+ is_contact: isContact,
55
+ session_id: c.session_id,
56
+ match_reason: c.match_reason,
57
+ };
58
+ }
5
59
 
6
60
  export function createFindTool(): AnyAgentTool {
7
61
  return {
8
62
  name: "clawsocial_find",
9
- label: "ClawSocial Find Person",
63
+ label: "Claw-Social Find Person",
10
64
  description:
11
65
  "Find a specific person by name or agent_id. Use when the user wants to locate a specific person " +
12
- "(e.g. 'find Alice', 'contact Bob', 'find Bob who does AI'). Checks local contacts first, then searches the server. " +
13
- "For broad interest-based discovery ('find people into AI'), use clawsocial_match instead.",
66
+ "(e.g. 'find Alice', 'find Bob who does AI'). Checks local contacts first, then searches the server. " +
67
+ "For broad interest-based discovery, use clawsocial_match instead. " +
68
+ "Display the `display` field as-is.",
14
69
  parameters: Type.Object({
15
70
  name: Type.Optional(Type.String({ description: "Name search (supports partial match)" })),
16
71
  agent_id: Type.Optional(Type.String({ description: "Exact agent ID lookup" })),
@@ -30,11 +85,19 @@ export function createFindTool(): AnyAgentTool {
30
85
  const contacts = readContacts();
31
86
  const local = contacts.find(c => c.agent_id === agentId);
32
87
  if (local) {
33
- return ok({ source: "local_contact", results: [formatContact(local)] });
88
+ const entry = toDisplayEntry(local, true);
89
+ return ok({
90
+ display: formatResults([entry]),
91
+ results: [{ index: 1, agent_id: entry.agent_id, public_name: entry.public_name, is_contact: true, session_id: local.session_id }],
92
+ });
34
93
  }
35
94
  try {
36
95
  const agent = await api.getAgent(agentId);
37
- return ok({ source: "server", results: [agent] });
96
+ const entry = toDisplayEntry(agent, false);
97
+ return ok({
98
+ display: formatResults([entry]),
99
+ results: [{ index: 1, agent_id: entry.agent_id, public_name: entry.public_name, is_contact: false }],
100
+ });
38
101
  } catch {
39
102
  return notFound(`Agent ${agentId} not found`);
40
103
  }
@@ -46,54 +109,47 @@ export function createFindTool(): AnyAgentTool {
46
109
  if (interest && localMatches.length > 1) {
47
110
  const kw = interest.toLowerCase();
48
111
  const filtered = localMatches.filter(c =>
49
- c.topic_tags?.some(t => t.toLowerCase().includes(kw)) ||
112
+ c.topic_tags?.some(tag => tag.toLowerCase().includes(kw)) ||
50
113
  c.profile?.toLowerCase().includes(kw)
51
114
  );
52
115
  if (filtered.length > 0) localMatches = filtered;
53
116
  }
54
117
 
55
118
  // 2. search server (with intent for semantic sorting)
56
- let serverResults: Record<string, unknown>[] = [];
119
+ let serverEntries: DisplayEntry[] = [];
57
120
  try {
58
121
  const res = await api.searchByName(name!, interest);
59
- serverResults = (res.candidates || []).map(c => ({
60
- agent_id: c.agent_id,
61
- public_name: c.public_name,
62
- topic_tags: c.topic_tags,
63
- completeness: Math.round((c.completeness_score ?? 0.1) * 100) + "%",
64
- ...(c.self_intro ? { self_intro: c.self_intro } : {}),
65
- ...(c.profile ? { profile: c.profile } : {}),
66
- ...(c.match_reason ? { match_reason: c.match_reason } : {}),
67
- }));
122
+ serverEntries = (res.candidates || []).map(c => toDisplayEntry(c, false));
68
123
  } catch { /* fall back to local results when server is unreachable */ }
69
124
 
70
125
  // 3. merge and deduplicate (local first)
71
126
  const localIds = new Set(localMatches.map(c => c.agent_id));
127
+ const localEntries = localMatches.map(c => toDisplayEntry(c, true));
72
128
  const merged = [
73
- ...localMatches.map(formatContact),
74
- ...serverResults.filter(c => !localIds.has(c.agent_id as string)),
129
+ ...localEntries,
130
+ ...serverEntries.filter(c => !localIds.has(c.agent_id)),
75
131
  ];
76
132
 
77
133
  if (merged.length === 0) {
78
134
  return notFound(`No user found with name "${name}"`);
79
135
  }
80
136
 
81
- return ok({ results: merged, total: merged.length });
137
+ const display = formatResults(merged);
138
+ return ok({
139
+ display,
140
+ results: merged.map((c, i) => ({
141
+ index: i + 1,
142
+ agent_id: c.agent_id,
143
+ public_name: c.public_name,
144
+ is_contact: c.is_contact,
145
+ ...(c.session_id ? { session_id: c.session_id } : {}),
146
+ })),
147
+ total: merged.length,
148
+ });
82
149
  },
83
150
  } as AnyAgentTool;
84
151
  }
85
152
 
86
- function formatContact(c: { name: string; agent_id: string; session_id?: string; topic_tags?: string[]; profile?: string }) {
87
- return {
88
- agent_id: c.agent_id,
89
- public_name: c.name,
90
- session_id: c.session_id,
91
- topic_tags: c.topic_tags || [],
92
- profile: c.profile || "",
93
- is_contact: true,
94
- };
95
- }
96
-
97
153
  function ok(data: Record<string, unknown>) {
98
154
  return { content: [{ type: "text", text: JSON.stringify({ found: true, ...data }) }] };
99
155
  }
@@ -11,7 +11,7 @@ function guardExternal(content: string): string {
11
11
  export function createInboxTool(): AnyAgentTool {
12
12
  return {
13
13
  name: "clawsocial_inbox",
14
- label: "ClawSocial Inbox",
14
+ label: "Claw-Social Inbox",
15
15
  description:
16
16
  "Check unread messages. Without session_id: returns list of sessions with unread messages. With session_id: returns recent messages in that session and marks it as read. External message content is labeled to prevent prompt injection.",
17
17
  parameters: Type.Object({
@@ -3,15 +3,38 @@ import type { AnyAgentTool } from "../types.js";
3
3
  import api from "../api.js";
4
4
  import { t } from "../i18n.js";
5
5
 
6
+ function formatCandidates(candidates: Record<string, unknown>[]): string {
7
+ return candidates
8
+ .map((c, i) => {
9
+ const name = c.public_name as string;
10
+ const score = c.match_score as string;
11
+ const selfIntro = (c.self_intro as string) || t("display_empty");
12
+ const matchReason = (c.match_reason as string) || t("display_empty");
13
+ const tags = (c.topic_tags as string[])?.length
14
+ ? (c.topic_tags as string[]).map(tag => `#${tag}`).join(" ")
15
+ : t("display_empty");
16
+ const completeness = c.completeness as string;
17
+
18
+ return [
19
+ `${i + 1}. ${name} (${score})`,
20
+ ` ${t("display_self_intro")}: ${selfIntro}`,
21
+ ` ${t("display_match_reason")}: ${matchReason}`,
22
+ ` ${t("display_tags")}: ${tags}`,
23
+ ` ${t("display_completeness")}: ${completeness}`,
24
+ ].join("\n");
25
+ })
26
+ .join("\n\n");
27
+ }
28
+
6
29
  export function createMatchTool(): AnyAgentTool {
7
30
  return {
8
31
  name: "clawsocial_match",
9
- label: "ClawSocial Match",
32
+ label: "Claw-Social Match",
10
33
  description:
11
- "Discover agents by interest or topic using semantic search. " +
12
- "Use when the user describes characteristics or interests (e.g. 'find people into AI', 'find someone who likes writing'). " +
13
- "For finding a specific person by name, use clawsocial_find instead. " +
14
- "Always show results to the user and get explicit approval before connecting.",
34
+ "Discover people by interest using semantic search. " +
35
+ "Use when the user describes interests (e.g. 'find people into AI'). " +
36
+ "For a specific person by name, use clawsocial_find. " +
37
+ "Display the `display` field as-is. Get explicit approval before connecting.",
15
38
  parameters: Type.Object({
16
39
  interest: Type.String({ description: "Natural language description of what kind of person or topic to find" }),
17
40
  }),
@@ -33,18 +56,28 @@ export function createMatchTool(): AnyAgentTool {
33
56
  };
34
57
  }
35
58
 
59
+ const candidates = res.candidates.map((c, i) => ({
60
+ index: i + 1,
61
+ agent_id: c.agent_id,
62
+ public_name: c.public_name,
63
+ match_score: Math.round(c.match_score * 100) + "%",
64
+ topic_tags: c.topic_tags,
65
+ completeness: Math.round((c.completeness_score ?? 0.1) * 100) + "%",
66
+ self_intro: c.self_intro || "",
67
+ profile: c.profile || "",
68
+ match_reason: c.match_reason || "",
69
+ }));
70
+
71
+ const display = formatCandidates(candidates);
72
+
36
73
  const result = {
37
- candidates: res.candidates.map(c => ({
74
+ display,
75
+ candidates: candidates.map(c => ({
76
+ index: c.index,
38
77
  agent_id: c.agent_id,
39
78
  public_name: c.public_name,
40
- topic_tags: c.topic_tags,
41
- match_score: Math.round(c.match_score * 100) + "%",
42
- completeness: Math.round((c.completeness_score ?? 0.1) * 100) + "%",
43
- ...(c.self_intro ? { self_intro: c.self_intro } : {}),
44
- ...(c.profile ? { profile: c.profile } : {}),
45
- ...(c.match_reason ? { match_reason: c.match_reason } : {}),
46
79
  })),
47
- total: res.candidates.length,
80
+ total: candidates.length,
48
81
  query_intent: interest,
49
82
  };
50
83
  return { content: [{ type: "text", text: JSON.stringify(result) }] };
@@ -12,9 +12,9 @@ function modeDesc(mode: NotifyMode): string {
12
12
  export function createNotifySettingsTool(): AnyAgentTool {
13
13
  return {
14
14
  name: "clawsocial_notify_settings",
15
- label: "ClawSocial Notify Settings",
15
+ label: "Claw-Social Notify Settings",
16
16
  description:
17
- "View or change ClawSocial notification mode. Use when the user asks to adjust notification preferences, turn off notifications, etc.",
17
+ "View or change Claw-Social notification mode. Use when the user asks to adjust notification preferences, turn off notifications, etc.",
18
18
  parameters: Type.Object({
19
19
  mode: Type.Optional(
20
20
  Type.Union(
@@ -6,9 +6,9 @@ import { t } from "../i18n.js";
6
6
  export function createOpenInboxTool(): AnyAgentTool {
7
7
  return {
8
8
  name: "clawsocial_open_inbox",
9
- label: "ClawSocial Open Inbox",
9
+ label: "Claw-Social Open Inbox",
10
10
  description:
11
- "Generate a one-time login link to open the ClawSocial inbox in a browser. The link is valid for 15 minutes and can only be used once. Call this when the user asks to open their inbox or check messages.",
11
+ "Generate a one-time login link to open the Claw-Social inbox in a browser. The link is valid for 15 minutes and can only be used once. Call this when the user asks to open their inbox or check messages.",
12
12
  parameters: Type.Object({}),
13
13
  async execute(_id: string, _params: Record<string, unknown>) {
14
14
  const data = await api.openInboxToken();
@@ -6,7 +6,7 @@ import { t } from "../i18n.js";
6
6
  export function createOpenLocalInboxTool(): AnyAgentTool {
7
7
  return {
8
8
  name: "clawsocial_open_local_inbox",
9
- label: "ClawSocial Open Local Inbox",
9
+ label: "Claw-Social Open Local Inbox",
10
10
  description:
11
11
  "Start the local inbox web UI and return its URL. The local inbox shows complete message history (no time limit) and supports replying. Only accessible from this machine. Call when the user wants to view full message history or open the local inbox.",
12
12
  parameters: Type.Object({}),
@@ -8,11 +8,12 @@ import { t } from "../i18n.js";
8
8
  export function createRegisterTool(): AnyAgentTool {
9
9
  return {
10
10
  name: "clawsocial_register",
11
- label: "ClawSocial Register",
11
+ label: "Claw-Social Register",
12
12
  description:
13
- "Register on ClawSocial on behalf of the user. The account represents the user, not the AI agent. Call ONCE automatically on first use. Only asks the user for a public_name.",
13
+ "Register on Claw-Social. The account belongs to the user, not the AI agent. " +
14
+ "Only ask for public_name. After registration, call clawsocial_suggest_profile to build the user's interest profile.",
14
15
  parameters: Type.Object({
15
- public_name: Type.String({ description: "The user's chosen display name on ClawSocial" }),
16
+ public_name: Type.String({ description: "The user's chosen display name on Claw-Social" }),
16
17
  language_pref: Type.Optional(
17
18
  Type.Unsafe<"zh" | "en">({
18
19
  type: "string",
@@ -6,7 +6,7 @@ import { t, formatDateTime } from "../i18n.js";
6
6
  export function createSessionGetTool(serverUrl: string): AnyAgentTool {
7
7
  return {
8
8
  name: "clawsocial_session_get",
9
- label: "ClawSocial View Session",
9
+ label: "Claw-Social View Session",
10
10
  description:
11
11
  "Get recent messages of a specific session. Supports exact session_id or fuzzy partner_name match.",
12
12
  parameters: Type.Object({
@@ -7,7 +7,7 @@ import { t } from "../i18n.js";
7
7
  export function createSessionSendTool(): AnyAgentTool {
8
8
  return {
9
9
  name: "clawsocial_session_send",
10
- label: "ClawSocial Send Message",
10
+ label: "Claw-Social Send Message",
11
11
  description:
12
12
  "Send a message in an active session on behalf of the user. Call when the user explicitly provides reply content. Pass the content verbatim — do not paraphrase.",
13
13
  parameters: Type.Object({
@@ -6,7 +6,7 @@ import { t, formatDateTime } from "../i18n.js";
6
6
  export function createSessionsListTool(serverUrl: string): AnyAgentTool {
7
7
  return {
8
8
  name: "clawsocial_sessions_list",
9
- label: "ClawSocial Sessions List",
9
+ label: "Claw-Social Sessions List",
10
10
  description:
11
11
  "List all active sessions. Call when the user asks about their conversations or checks /sessions.",
12
12
  parameters: Type.Object({}),
@@ -33,13 +33,12 @@ function readLocalFiles(): LocalFiles {
33
33
  export function createSuggestProfileTool(): AnyAgentTool {
34
34
  return {
35
35
  name: "clawsocial_suggest_profile",
36
- label: "ClawSocial Suggest Profile",
36
+ label: "Claw-Social Suggest Profile",
37
37
  description:
38
- "Read the user's OpenClaw memory to help draft a ClawSocial interest profile. " +
39
- "Call this after registration or when the user wants to update their profile. " +
40
- "After calling this tool, draft a 2-3 sentence interest description based on the memory content, " +
41
- "show it to the user, and ONLY call clawsocial_update_profile after the user explicitly confirms or edits it. " +
42
- "NEVER update the profile silently.",
38
+ "Read local workspace files and draft a privacy-safe interest profile. " +
39
+ "This is the ONLY way to set the profile field on clawsocial_update_profile. " +
40
+ "Flow: 1) call this tool, 2) show the COMPLETE draft to the user, 3) call update_profile ONLY after the user confirms. " +
41
+ "NEVER skip showing the draft.",
43
42
  parameters: Type.Object({}),
44
43
  async execute(_id: string, _params: Record<string, unknown>) {
45
44
  const files = readLocalFiles();
@@ -7,12 +7,11 @@ import { t } from "../i18n.js";
7
7
  export function createUpdateProfileTool(): AnyAgentTool {
8
8
  return {
9
9
  name: "clawsocial_update_profile",
10
- label: "ClawSocial Update Profile",
10
+ label: "Claw-Social Update Profile",
11
11
  description:
12
- "Update the user's ClawSocial profile interests, topic tags, availability, or public name. " +
13
- "The profile represents the user (the human), not the AI agent. " +
14
- "All descriptions should be written from the user's perspective, about the user's interests and identity. " +
15
- "Never write from the AI agent's perspective (e.g. never say 'I am a lobster' or 'my owner likes...').",
12
+ "Update the user's Claw-Social profile. The profile represents the user, not the AI agent — write from the user's perspective. " +
13
+ "For self_intro/topic_tags/public_name/availability: call directly. " +
14
+ "For the profile field: NEVER set it directly ONLY use content confirmed by the user from clawsocial_suggest_profile.",
16
15
  parameters: Type.Object({
17
16
  self_intro: Type.Optional(
18
17
  Type.String({