@pnds/pond 1.2.0 → 1.2.1

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/TOOLS.md CHANGED
@@ -23,32 +23,22 @@ Typical wiki edit flow:
23
23
  4. `wiki_status` and `wiki_diff` to verify the exact change
24
24
  5. `wiki_propose` to submit or update the wiki changeset
25
25
 
26
- ## Pond CLI
26
+ ## Pond CLI (read-only)
27
27
 
28
- You also have access to the Pond platform via the `@pnds/cli` CLI. Auth is pre-configured.
28
+ You have read-only access to the Pond platform via the `@pnds/cli` CLI. Auth is pre-configured.
29
+
30
+ > **DO NOT use the CLI to send messages.** Use `pond_reply` for chat replies and `pond_dm` for direct messages.
31
+ > **DO NOT use the CLI to create tasks.** Use task tools or sub-agents instead.
29
32
 
30
33
  ```bash
31
34
  npx @pnds/cli@latest whoami # Check your identity
32
- npx @pnds/cli@latest agents list # List all agents (name, model, status)
33
- npx @pnds/cli@latest dm <name-or-id> --text "..." # DM a user by name or ID (auto-creates chat)
34
- npx @pnds/cli@latest dm <name-or-id> --text "..." --no-reply # DM, signal no reply expected
35
+ npx @pnds/cli@latest agents list # List all agents
35
36
  npx @pnds/cli@latest chats list # List chats
36
37
  npx @pnds/cli@latest messages list <chatId> # Read chat history
37
- npx @pnds/cli@latest messages send <chatId> --text "..." # Send a message
38
- npx @pnds/cli@latest messages send <chatId> --text "..." --no-reply # Send, no reply expected
39
38
  npx @pnds/cli@latest tasks list [--status ...] # List tasks
40
- npx @pnds/cli@latest tasks create --title "..." # Create a task
41
- npx @pnds/cli@latest tasks update <taskId> --status in_progress
42
39
  npx @pnds/cli@latest projects list # List projects
43
40
  npx @pnds/cli@latest users search <query> # Find users
44
41
  npx @pnds/cli@latest members list # List org members
45
42
  ```
46
43
 
47
44
  Run `npx @pnds/cli@latest --help` or `npx @pnds/cli@latest <command> --help` for full options. Output is JSON.
48
-
49
- ## Agent-to-Agent Interaction
50
-
51
- When you receive a message from another agent (not a human):
52
- - If the message has a no_reply hint, you may still reason and use tools, but your response will not be sent to chat.
53
- - If you have nothing meaningful to add to the conversation, produce an empty response to avoid unnecessary back-and-forth.
54
- - Use --no-reply when sending messages that don't require a response (e.g., delivering results, FYI notifications).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnds/pond",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "OpenClaw channel plugin for Pond IM",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -16,8 +16,8 @@
16
16
  ],
17
17
  "dependencies": {
18
18
  "@sinclair/typebox": "^0.34.48",
19
- "@pnds/cli": "1.2.0",
20
- "@pnds/sdk": "1.2.0"
19
+ "@pnds/sdk": "1.2.1",
20
+ "@pnds/cli": "1.2.1"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@types/node": "^22.0.0",
@@ -50,6 +50,7 @@ function resolvePondState(ctx: ToolSessionContext) {
50
50
  const ReplySchema = Type.Object({
51
51
  chatId: Type.String({ minLength: 1, description: "Chat ID to send the message to." }),
52
52
  text: Type.String({ minLength: 1, description: "Message text." }),
53
+ threadRootId: Type.Optional(Type.String({ description: "If provided, send the message as a thread reply under this root message ID." })),
53
54
  noReply: Type.Optional(Type.Boolean({ description: "If true, hints that no reply is expected." })),
54
55
  });
55
56
 
@@ -78,6 +79,12 @@ const TypingSchema = Type.Object({
78
79
  action: Type.Union([Type.Literal("start"), Type.Literal("stop")], { description: "Start or stop the typing indicator." }),
79
80
  });
80
81
 
82
+ const DmSchema = Type.Object({
83
+ userId: Type.String({ minLength: 1, description: "Target user ID to DM." }),
84
+ text: Type.String({ minLength: 1, description: "Message text." }),
85
+ noReply: Type.Optional(Type.Boolean({ description: "If true, hints that no reply is expected." })),
86
+ });
87
+
81
88
  // ---- Helpers ----
82
89
 
83
90
  function formatMessage(msg: Message): string {
@@ -110,6 +117,7 @@ function createActionTools(toolCtx: ToolSessionContext): AnyAgentTool[] {
110
117
  const s = state();
111
118
  const chatId = readStringParam(params, "chatId", { required: true });
112
119
  const text = readStringParam(params, "text", { required: true });
120
+ const threadRootId = readStringParam(params, "threadRootId");
113
121
  const noReply = params.noReply === true;
114
122
  const runId = s.activeRuns && toolCtx.sessionKey
115
123
  ? await getActiveRunId(s, toolCtx.sessionKey)
@@ -134,6 +142,7 @@ function createActionTools(toolCtx: ToolSessionContext): AnyAgentTool[] {
134
142
  message_type: "text",
135
143
  content: { text },
136
144
  agent_run_id: runId,
145
+ ...(threadRootId ? { thread_root_id: threadRootId } : {}),
137
146
  ...(noReply ? { hints: { no_reply: true } } : {}),
138
147
  });
139
148
  return jsonResult({ message_id: result.id, chat_id: chatId });
@@ -212,12 +221,36 @@ function createActionTools(toolCtx: ToolSessionContext): AnyAgentTool[] {
212
221
  return jsonResult({ ok: true });
213
222
  },
214
223
  },
224
+ {
225
+ label: "Pond DM",
226
+ name: "pond_dm",
227
+ description: "Send a direct message to a user by ID. Automatically finds or creates the DM chat.",
228
+ parameters: DmSchema,
229
+ execute: async (_toolCallId, params) => {
230
+ const s = state();
231
+ const userId = readStringParam(params, "userId", { required: true });
232
+ const text = readStringParam(params, "text", { required: true });
233
+ const noReply = params.noReply === true;
234
+ const runId = s.activeRuns && toolCtx.sessionKey
235
+ ? await getActiveRunId(s, toolCtx.sessionKey)
236
+ : undefined;
237
+
238
+ const result = await s.client.sendDirectMessage(s.orgId, {
239
+ user_id: userId,
240
+ message_type: "text",
241
+ content: { text },
242
+ agent_run_id: runId,
243
+ ...(noReply ? { hints: { no_reply: true } } : {}),
244
+ });
245
+ return jsonResult({ chat_id: result.chat.id, message_id: result.message.id });
246
+ },
247
+ },
215
248
  ];
216
249
  }
217
250
 
218
251
  export function registerPondActionTools(api: OpenClawPluginApi) {
219
252
  api.registerTool(
220
253
  (ctx) => createActionTools(ctx),
221
- { names: ["pond_reply", "pond_task_comment", "pond_task_update", "pond_chat_history", "pond_typing"] },
254
+ { names: ["pond_reply", "pond_task_comment", "pond_task_update", "pond_chat_history", "pond_typing", "pond_dm"] },
222
255
  );
223
256
  }
@@ -19,6 +19,13 @@ interface ConfigManagerOpts {
19
19
 
20
20
  const CACHE_FILENAME = "pond-platform-config.json";
21
21
 
22
+ /** All tool names registered by this plugin — must stay in sync with registerPondActionTools + registerPondWikiTools. */
23
+ const POND_TOOL_NAMES = [
24
+ "pond_reply", "pond_dm", "pond_task_comment", "pond_task_update", "pond_chat_history", "pond_typing",
25
+ "wiki_list", "wiki_status", "wiki_diff", "wiki_tree", "wiki_blob",
26
+ "wiki_search", "wiki_query", "wiki_outline", "wiki_section", "wiki_changeset_diff", "wiki_propose",
27
+ ];
28
+
22
29
  function cachePath(stateDir: string): string {
23
30
  return path.join(stateDir, CACHE_FILENAME);
24
31
  }
@@ -91,6 +98,17 @@ function applyToOpenClawConfig(
91
98
  existing.agents = agents;
92
99
  }
93
100
 
101
+ // Ensure Pond plugin tools are allowed alongside whatever tools.profile is set.
102
+ // Uses `alsoAllow` (additive) so user's profile choice is preserved.
103
+ const tools =
104
+ typeof existing.tools === "object"
105
+ && existing.tools !== null
106
+ && !Array.isArray(existing.tools)
107
+ ? (existing.tools as Record<string, unknown>)
108
+ : {};
109
+ tools.alsoAllow = POND_TOOL_NAMES;
110
+ existing.tools = tools;
111
+
94
112
  // Atomic write
95
113
  const tmpPath = `${configPath}.tmp`;
96
114
  fs.mkdirSync(path.dirname(configPath), { recursive: true });
package/src/hooks.ts CHANGED
@@ -25,32 +25,63 @@ async function resolveClientForHook(sessionKey: string | undefined) {
25
25
  return { ...state, chatId, activeRunId };
26
26
  }
27
27
 
28
- const ORCHESTRATOR_PREFIX = `## Orchestrator Model
28
+ const ORCHESTRATOR_PREFIX = `## CRITICAL — Pond Orchestrator Mode
29
29
 
30
- You are an orchestrator agent. Events arrive as structured \`[Event: ...]\` blocks. Your text output is internal reasoning only users cannot see it. All visible actions must go through tools.
30
+ > **Your text output is completely invisible.** No user or agent will ever see
31
+ > anything you write as plain text. The ONLY way to communicate is through
32
+ > Pond action tools. If you respond with text alone, your response is silently
33
+ > discarded.
31
34
 
32
- ## Pond Action Tools
35
+ You are the Pond orchestrator. Events from multiple chats and tasks arrive as
36
+ structured \`[Event: ...]\` blocks. You decide how to handle each event and act
37
+ through tools.
33
38
 
34
- - \`pond_reply\` Send a text message to a chat. Params: \`chatId\`, \`text\`, optional \`noReply\`.
39
+ ### How to Reply to a Message
40
+
41
+ When you receive \`[Event: message.new]\` with \`[Chat: ... (cht_abc123)]\`:
42
+
43
+ pond_reply(chatId: "cht_abc123", text: "Your response here")
44
+
45
+ If the event includes \`[Thread: 01JWC...]\`, reply inside the thread:
46
+
47
+ pond_reply(chatId: "cht_abc123", threadRootId: "01JWC...", text: "Thread reply here")
48
+
49
+ DO NOT write text. ONLY \`pond_reply\` delivers messages to users.
50
+
51
+ ### How to Act on a Task
52
+
53
+ When you receive \`[Event: task.assigned]\` with \`[Task: ... (tsk_xyz789)]\`:
54
+
55
+ - \`pond_task_comment(taskId: "tsk_xyz789", body: "Starting work...")\` — post progress
56
+ - \`pond_task_update(taskId: "tsk_xyz789", status: "in_progress")\` — change status
57
+
58
+ ### Pond Action Tools
59
+
60
+ - \`pond_reply\` — Send a text message to a chat. Params: \`chatId\`, \`text\`, optional \`threadRootId\`, \`noReply\`.
61
+ - \`pond_dm\` — Send a direct message to a user by ID (auto-creates DM chat). Params: \`userId\`, \`text\`, optional \`noReply\`. Use this for proactive outreach (e.g., notifying a task assigner of completion).
35
62
  - \`pond_task_comment\` — Post a comment on a task. Params: \`taskId\`, \`body\`.
36
63
  - \`pond_task_update\` — Update task fields (status, title, description, priority). Params: \`taskId\`, plus optional fields.
37
- - \`pond_chat_history\` — Read recent messages from a chat (default 20, max 50). Use this to get context on unfamiliar chats. Params: \`chatId\`, optional \`limit\`, \`before\`, \`threadRootId\`.
64
+ - \`pond_chat_history\` — Read recent messages from a chat (default 20, max 50). Params: \`chatId\`, optional \`limit\`, \`before\`, \`threadRootId\`.
38
65
  - \`pond_typing\` — Start or stop the typing indicator. Params: \`chatId\`, \`action\` ("start" | "stop").
39
66
 
40
- ## Task Handling
41
-
42
- Use \`pond_task_comment\` to communicate progress on tasks. Use \`pond_task_update\` to change task status (e.g., in_progress, done), title, description, or priority.
43
-
44
- ## Sub-Agents
67
+ ### Sub-Agents
45
68
 
46
69
  For long-running tasks, use \`sessions_spawn\` to delegate to a sub-agent. Return quickly so the orchestrator can process other events.
47
70
 
48
- ## Agent-to-Agent Interaction
71
+ When a sub-agent completes and you receive a completion event, deliver the result to the relevant chat using \`pond_reply\` (or \`pond_dm\` if the target is a user, not a chat). Do NOT rely on text output — it is invisible.
72
+
73
+ ### Agent-to-Agent Interaction
49
74
 
50
75
  When you receive a message from another agent (not a human):
51
- - If the message has a no_reply hint, you may still reason and use tools, but your response will not be sent to chat.
52
- - If you have nothing meaningful to add to the conversation, produce an empty response to avoid unnecessary back-and-forth.
53
- - Replies go through \`pond_reply\` use \`noReply: true\` when sending messages that don't require a response (e.g., delivering results, FYI notifications).`;
76
+ - If the message has a \`no_reply\` hint, do not call \`pond_reply\`. You may still reason and use other tools.
77
+ - If you have nothing meaningful to add, produce an empty response to avoid unnecessary back-and-forth.
78
+ - Use \`pond_reply\` with \`noReply: true\` when sending messages that don't need a response (FYI, results delivery).
79
+
80
+ ### Prohibitions
81
+
82
+ - **DO NOT** write text responses — they are invisible and silently discarded.
83
+ - **DO NOT** use the Pond CLI (\`@pnds/cli\`) to send messages — use \`pond_reply\` for chat replies and \`pond_dm\` for direct messages.
84
+ - **DO NOT** use the \`message\` tool to send to Pond chats — use \`pond_reply\`.`;
54
85
 
55
86
  // Load wiki tools, CLI docs, and other tool docs from TOOLS.md (maintained separately)
56
87
  const POND_ORCHESTRATOR_CONTEXT = ORCHESTRATOR_PREFIX + "\n\n" + loadToolsMarkdown();
@@ -60,7 +91,7 @@ export function registerPondHooks(api: OpenClawPluginApi) {
60
91
 
61
92
  // Inject CLI awareness into agent system prompt
62
93
  api.on("before_prompt_build", () => {
63
- return { prependContext: POND_ORCHESTRATOR_CONTEXT };
94
+ return { prependSystemContext: POND_ORCHESTRATOR_CONTEXT };
64
95
  });
65
96
 
66
97
  // before_tool_call -> send tool_call step to AgentRun