@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 +6 -16
- package/package.json +3 -3
- package/src/action-tools.ts +34 -1
- package/src/config-manager.ts +18 -0
- package/src/hooks.ts +46 -15
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
|
|
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
|
|
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.
|
|
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/
|
|
20
|
-
"@pnds/
|
|
19
|
+
"@pnds/sdk": "1.2.1",
|
|
20
|
+
"@pnds/cli": "1.2.1"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@types/node": "^22.0.0",
|
package/src/action-tools.ts
CHANGED
|
@@ -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
|
}
|
package/src/config-manager.ts
CHANGED
|
@@ -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
|
|
28
|
+
const ORCHESTRATOR_PREFIX = `## CRITICAL — Pond Orchestrator Mode
|
|
29
29
|
|
|
30
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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).
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
52
|
-
- If you have nothing meaningful to add
|
|
53
|
-
-
|
|
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 {
|
|
94
|
+
return { prependSystemContext: POND_ORCHESTRATOR_CONTEXT };
|
|
64
95
|
});
|
|
65
96
|
|
|
66
97
|
// before_tool_call -> send tool_call step to AgentRun
|