@pnds/pond 1.5.0 → 1.6.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.
@@ -1,6 +1,11 @@
1
1
  {
2
2
  "id": "pond",
3
3
  "channels": ["pond"],
4
+ "skills": [
5
+ "./skills/pond-wiki",
6
+ "./skills/pond-tasks",
7
+ "./skills/pond-platform"
8
+ ],
4
9
  "configSchema": {
5
10
  "type": "object",
6
11
  "additionalProperties": true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnds/pond",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "OpenClaw channel plugin for Pond IM",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -11,13 +11,12 @@
11
11
  "type": "module",
12
12
  "files": [
13
13
  "src",
14
- "TOOLS.md",
14
+ "skills",
15
15
  "openclaw.plugin.json"
16
16
  ],
17
17
  "dependencies": {
18
- "@sinclair/typebox": "^0.34.48",
19
- "@pnds/cli": "1.5.0",
20
- "@pnds/sdk": "1.5.0"
18
+ "@pnds/cli": "1.6.0",
19
+ "@pnds/sdk": "1.6.0"
21
20
  },
22
21
  "devDependencies": {
23
22
  "@types/node": "^22.0.0",
@@ -0,0 +1,50 @@
1
+ ---
2
+ name: pond-platform
3
+ description: "Pond platform queries: list org members, agents, chats, read message history, check identity. Use when: user asks about org members, who's online, chat history, agent list, or identity/auth questions."
4
+ ---
5
+
6
+ # Pond Platform
7
+
8
+ Query organization data via the Pond CLI. Auth is pre-configured.
9
+
10
+ ## Identity
11
+
12
+ ```bash
13
+ npx @pnds/cli@latest whoami
14
+ ```
15
+
16
+ ## Members & Agents
17
+
18
+ ```bash
19
+ npx @pnds/cli@latest members list # All org members (humans + agents)
20
+ npx @pnds/cli@latest agents list # Agents only
21
+ npx @pnds/cli@latest users get usr_xxx # Get user details by ID
22
+ ```
23
+
24
+ ## Chats & Messages
25
+
26
+ ```bash
27
+ npx @pnds/cli@latest chats list # List all chats
28
+ npx @pnds/cli@latest messages list cht_xxx # Read chat message history
29
+ ```
30
+
31
+ ## Sending Messages
32
+
33
+ Each `[Event: message.new]` includes `[Chat: ... (cht_xxx)]` — always use that chat ID explicitly.
34
+
35
+ ```bash
36
+ # Reply to a chat (use the chat ID from the event)
37
+ npx @pnds/cli@latest messages send cht_xxx --text "Your reply"
38
+
39
+ # Direct message by user ID or display name
40
+ npx @pnds/cli@latest dm usr_xxx --text "Hello"
41
+ npx @pnds/cli@latest dm "Alice" --text "Hello"
42
+
43
+ # Thread reply
44
+ npx @pnds/cli@latest messages send cht_xxx --text "Reply" --thread-root-id 01JWC...
45
+
46
+ # FYI message (no response expected)
47
+ npx @pnds/cli@latest messages send cht_xxx --text "FYI: done" --no-reply
48
+ ```
49
+
50
+ All CLI output is JSON.
@@ -0,0 +1,44 @@
1
+ ---
2
+ name: pond-tasks
3
+ description: "Pond task operations: create, update, comment on, and query tasks and projects. Use when: user asks to create a task, update task status, add comments, list tasks, or manage projects."
4
+ ---
5
+
6
+ # Pond Tasks
7
+
8
+ Manage tasks and projects via the Pond CLI. Auth and runtime context are pre-configured.
9
+
10
+ ## Task Commands
11
+
12
+ ```bash
13
+ # List tasks (filterable by status)
14
+ npx @pnds/cli@latest tasks list
15
+ npx @pnds/cli@latest tasks list --status todo
16
+ npx @pnds/cli@latest tasks list --status in_progress
17
+
18
+ # Create a task
19
+ npx @pnds/cli@latest tasks create --title "Task title" [--assignee-id usr_xxx] [--project-id prj_xxx]
20
+
21
+ # Update task status
22
+ npx @pnds/cli@latest tasks update tsk_xxx --status in_progress
23
+ npx @pnds/cli@latest tasks update tsk_xxx --status done
24
+
25
+ # Add a comment
26
+ npx @pnds/cli@latest tasks comments add tsk_xxx --body "Progress update..."
27
+ ```
28
+
29
+ ## Project Commands
30
+
31
+ ```bash
32
+ npx @pnds/cli@latest projects list
33
+ ```
34
+
35
+ ## Responding to Task Assignments
36
+
37
+ When you receive `[Event: task.assigned]`:
38
+
39
+ 1. Acknowledge with a comment: `tasks comments add tsk_xxx --body "On it"`
40
+ 2. Update status: `tasks update tsk_xxx --status in_progress`
41
+ 3. Do the work
42
+ 4. Report results via comment and update status to `done`
43
+
44
+ All CLI output is JSON.
@@ -0,0 +1,63 @@
1
+ ---
2
+ name: pond-wiki
3
+ description: "Pond wiki operations: read, search, edit, and propose changes to organization knowledge bases. Use when: user asks to read/write docs, edit wiki pages, search knowledge base, propose changes, or review changesets."
4
+ ---
5
+
6
+ # Pond Wiki
7
+
8
+ Manage organization wikis via the Pond CLI. Auth is pre-configured.
9
+
10
+ ## Browsing (no local state needed)
11
+
12
+ ```bash
13
+ npx @pnds/cli@latest wiki list # List all wikis
14
+ npx @pnds/cli@latest wiki tree <slug> # List files and directories
15
+ npx @pnds/cli@latest wiki tree <slug> --path docs/ # List a subdirectory
16
+ ```
17
+
18
+ ## Editing (requires local workspace)
19
+
20
+ Wiki editing uses a local workspace. Sync first, edit files with standard tools, then propose.
21
+
22
+ ```bash
23
+ # 1. Sync wiki to local workspace
24
+ npx @pnds/cli@latest wiki sync <slug>
25
+
26
+ # 2. Edit files locally (they live under POND_WIKI_MOUNT_ROOT)
27
+ # Use read/write/edit tools on the local files
28
+
29
+ # 3. Review changes
30
+ npx @pnds/cli@latest wiki status <slug> # Summary of local changes
31
+ npx @pnds/cli@latest wiki diff <slug> # Unified diff
32
+
33
+ # 4. Propose changeset
34
+ npx @pnds/cli@latest wiki changeset create <slug> --title "My change"
35
+ ```
36
+
37
+ ## Changeset Management
38
+
39
+ ```bash
40
+ npx @pnds/cli@latest wiki changeset list <slug> # List all changesets
41
+ npx @pnds/cli@latest wiki changeset show <changesetId> <slug> # Show detail + feedback
42
+ npx @pnds/cli@latest wiki changeset diff <changesetId> <slug> # Show changeset diff
43
+ ```
44
+
45
+ If a changeset is rejected, fix the files and re-propose:
46
+
47
+ ```bash
48
+ npx @pnds/cli@latest wiki changeset create <slug> --update <changesetId>
49
+ ```
50
+
51
+ ## History
52
+
53
+ ```bash
54
+ npx @pnds/cli@latest wiki log <slug> # Recent operations
55
+ ```
56
+
57
+ ## Tips
58
+
59
+ - Always `wiki diff` before proposing to verify the exact change.
60
+ - Use `wiki tree` to browse the file structure without syncing.
61
+ - After sync, read files directly from the local workspace — no need for a separate read command.
62
+
63
+ All CLI output is JSON. Run `npx @pnds/cli@latest wiki --help` for full options.
@@ -19,11 +19,7 @@ interface ConfigManagerOpts {
19
19
 
20
20
  const CACHE_FILENAME = "pond-platform-config.json";
21
21
 
22
- /** All tool names registered by this pluginmust stay in sync with registerPondWikiTools. */
23
- const POND_TOOL_NAMES = [
24
- "wiki_list", "wiki_status", "wiki_diff", "wiki_tree", "wiki_blob",
25
- "wiki_search", "wiki_query", "wiki_outline", "wiki_section", "wiki_changeset_diff", "wiki_propose",
26
- ];
22
+ /** Tool names are no longer registeredall operations go through CLI. */
27
23
 
28
24
  function cachePath(stateDir: string): string {
29
25
  return path.join(stateDir, CACHE_FILENAME);
@@ -97,16 +93,7 @@ function applyToOpenClawConfig(
97
93
  existing.agents = agents;
98
94
  }
99
95
 
100
- // Ensure Pond plugin tools are allowed alongside whatever tools.profile is set.
101
- // Uses `alsoAllow` (additive) so user's profile choice is preserved.
102
- const tools =
103
- typeof existing.tools === "object"
104
- && existing.tools !== null
105
- && !Array.isArray(existing.tools)
106
- ? (existing.tools as Record<string, unknown>)
107
- : {};
108
- tools.alsoAllow = POND_TOOL_NAMES;
109
- existing.tools = tools;
96
+ // No plugin tools to allow all operations go through CLI.
110
97
 
111
98
  // Atomic write
112
99
  const tmpPath = `${configPath}.tmp`;
package/src/hooks.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
2
  import { extractAccountIdFromSessionKey } from "./session.js";
3
3
  import { clearDispatchMessageId, clearSessionMessageId, getActiveRunId, getDispatchMessageId, getDispatchNoReply, getPondAccountState, getSessionChatId } from "./runtime.js";
4
- import { loadToolsMarkdown } from "./tools-md.js";
5
4
 
6
5
  /**
7
6
  * Resolve the PondClient + orgId + runId for a hook context.
@@ -25,87 +24,26 @@ async function resolveClientForHook(sessionKey: string | undefined) {
25
24
  return { ...state, chatId, activeRunId };
26
25
  }
27
26
 
28
- const ORCHESTRATOR_PREFIX = `## Pond Orchestrator Mode
27
+ const POND_CHANNEL_CONTEXT = `## Pond Channel — Message Delivery
29
28
 
30
- You are the Pond orchestrator. Events from multiple chats and tasks arrive as
31
- structured \`[Event: ...]\` blocks. You decide how to handle each event and
32
- respond using Bash commands via the Pond CLI.
29
+ Messages from the Pond channel arrive as \`[Event: ...]\` blocks.
30
+ Your text output is **not delivered to the user** it is discarded silently.
31
+ To reply, you **must** use the Pond CLI via the exec (Bash) tool.
33
32
 
34
- Your text output is internal (not delivered to users). Use the CLI to interact.
33
+ Each event includes \`[Chat: ... (cht_xxx)]\` use that chat ID to reply:
35
34
 
36
- ### Pond CLI Pre-Authenticated
35
+ npx @pnds/cli@latest messages send cht_xxx --text "Your reply here"
37
36
 
38
- The Pond CLI (\`npx @pnds/cli@latest\`) is pre-authenticated via environment
39
- variables injected into every Bash command. You do NOT need to set
40
- POND_API_URL, POND_API_KEY, etc. they are already configured.
41
-
42
- \`POND_CHAT_ID\` is set to the chat that triggered the current event. When
43
- replying to the trigger chat, you can omit the chatId argument.
44
-
45
- ### How to Reply to a Message
46
-
47
- When you receive \`[Event: message.new]\` with \`[Chat: ... (cht_abc123)]\`:
48
-
49
- npx @pnds/cli@latest messages send cht_abc123 --text "Your response here"
50
-
51
- Or, since POND_CHAT_ID is set to the trigger chat:
52
-
53
- npx @pnds/cli@latest messages send --text "Your response here"
54
-
55
- For thread replies (when the event includes \`[Thread: 01JWC...]\`):
56
-
57
- npx @pnds/cli@latest messages send --text "Thread reply" --thread-root-id 01JWC...
58
-
59
- ### How to Send a Direct Message
60
-
61
- npx @pnds/cli@latest dm usr_xyz --text "Hello"
62
-
63
- Or by display name:
64
-
65
- npx @pnds/cli@latest dm "Alice" --text "Hello"
66
-
67
- ### How to Act on a Task
68
-
69
- When you receive \`[Event: task.assigned]\` with \`[Task: ... (tsk_xyz789)]\`:
70
-
71
- npx @pnds/cli@latest tasks comments add tsk_xyz789 --body "Starting work..."
72
- npx @pnds/cli@latest tasks update tsk_xyz789 --status in_progress
73
-
74
- ### Reading Data
75
-
76
- npx @pnds/cli@latest messages list <chatId>
77
- npx @pnds/cli@latest tasks list --status open
78
- npx @pnds/cli@latest chats list
79
- npx @pnds/cli@latest members list
80
-
81
- ### Sub-Agents
82
-
83
- For long-running tasks, use \`sessions_spawn\` to delegate to a sub-agent.
84
- Return quickly so the orchestrator can process other events.
85
-
86
- When a sub-agent completes, deliver the result using the CLI:
87
-
88
- npx @pnds/cli@latest messages send <chatId> --text "Result: ..."
89
-
90
- ### Agent-to-Agent Interaction
91
-
92
- When a message has a \`[Hint: no_reply]\`, do not send a reply. The CLI
93
- automatically suppresses sends when POND_NO_REPLY is set, but you should
94
- also skip unnecessary computation.
95
-
96
- Use \`--no-reply\` for FYI messages that don't need a response:
97
-
98
- npx @pnds/cli@latest messages send <chatId> --text "FYI: done" --no-reply`;
99
-
100
- // Load wiki tools, CLI docs, and other tool docs from TOOLS.md (maintained separately)
101
- const POND_ORCHESTRATOR_CONTEXT = ORCHESTRATOR_PREFIX + "\n\n" + loadToolsMarkdown();
37
+ Credentials are pre-configured in every Bash command no setup needed.
38
+ Load the \`pond-platform\` skill for full CLI reference.
39
+ When a message has \`[Hint: no_reply]\`, do not send a reply.`;
102
40
 
103
41
  export function registerPondHooks(api: OpenClawPluginApi) {
104
42
  const log = api.logger;
105
43
 
106
- // Inject CLI awareness into agent system prompt
44
+ // Append Pond channel context AFTER workspace files so it takes precedence
107
45
  api.on("before_prompt_build", () => {
108
- return { prependSystemContext: POND_ORCHESTRATOR_CONTEXT };
46
+ return { appendSystemContext: POND_CHANNEL_CONTEXT };
109
47
  });
110
48
 
111
49
  // before_tool_call -> (1) fire-and-forget step creation, (2) ENV injection for exec tool
package/src/index.ts CHANGED
@@ -3,7 +3,6 @@ import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core";
3
3
  import { pondPlugin } from "./channel.js";
4
4
  import { setPondRuntime } from "./runtime.js";
5
5
  import { registerPondHooks } from "./hooks.js";
6
- import { registerPondWikiTools } from "./wiki-tools.js";
7
6
 
8
7
  const plugin = {
9
8
  id: "pond",
@@ -13,7 +12,6 @@ const plugin = {
13
12
  register(api: OpenClawPluginApi) {
14
13
  setPondRuntime(api.runtime);
15
14
  api.registerChannel({ plugin: pondPlugin });
16
- registerPondWikiTools(api);
17
15
  registerPondHooks(api);
18
16
  },
19
17
  };
package/TOOLS.md DELETED
@@ -1,62 +0,0 @@
1
- # Pond Tools
2
-
3
- ## Pond Wiki Tools
4
-
5
- Use native Pond wiki tools for wiki lookup and editing instead of shelling out to the CLI.
6
-
7
- - `wiki_list` to find candidate wikis
8
- - `wiki_query` as the primary tree-aware retrieval tool
9
- - `wiki_outline` to inspect the local structural outline of a wiki or path prefix
10
- - `wiki_search` as a simpler lexical fallback
11
- - `wiki_section` to expand a result by `nodeId`
12
- - `wiki_blob` only when you need the whole file
13
- - `wiki_status` to inspect local mounted wiki edits
14
- - `wiki_diff` to review the local mounted wiki patch
15
- - `wiki_propose` to open or update a wiki changeset after editing
16
- - `wiki_changeset_diff` to inspect a wiki draft or changeset
17
-
18
- Typical wiki edit flow:
19
-
20
- 1. `wiki_query` or `wiki_outline` to locate the right content
21
- 2. `wiki_section` to read the exact section body you need
22
- 3. Edit the mounted wiki files locally
23
- 4. `wiki_status` and `wiki_diff` to verify the exact change
24
- 5. `wiki_propose` to submit or update the wiki changeset
25
-
26
- ## Pond CLI
27
-
28
- You have full access to the Pond platform via the `@pnds/cli` CLI. Auth and
29
- runtime context (org, agent run ID, current chat) are pre-configured via
30
- environment variables — no manual setup needed.
31
-
32
- ### Sending Messages
33
-
34
- ```bash
35
- npx @pnds/cli@latest messages send [chatId] --text "..." # chatId defaults to POND_CHAT_ID
36
- npx @pnds/cli@latest messages send --text "..." --thread-root-id <id>
37
- npx @pnds/cli@latest messages send --text "..." --no-reply # FYI, no response expected
38
- npx @pnds/cli@latest dm <nameOrId> --text "..." # DM by user ID or display name
39
- ```
40
-
41
- ### Reading Data
42
-
43
- ```bash
44
- npx @pnds/cli@latest whoami # Check your identity
45
- npx @pnds/cli@latest agents list # List all agents
46
- npx @pnds/cli@latest chats list # List chats
47
- npx @pnds/cli@latest messages list <chatId> # Read chat history
48
- npx @pnds/cli@latest tasks list [--status ...] # List tasks
49
- npx @pnds/cli@latest projects list # List projects
50
- npx @pnds/cli@latest users get <userId> # Get user by ID
51
- npx @pnds/cli@latest members list # List org members
52
- ```
53
-
54
- ### Task Operations
55
-
56
- ```bash
57
- npx @pnds/cli@latest tasks comments add <taskId> --body "..."
58
- npx @pnds/cli@latest tasks update <taskId> --status in_progress
59
- npx @pnds/cli@latest tasks create --title "..." [--assignee-id ...] [--project-id ...]
60
- ```
61
-
62
- Run `npx @pnds/cli@latest --help` or `npx @pnds/cli@latest <command> --help` for full options. Output is JSON.
@@ -1,11 +0,0 @@
1
- /**
2
- * Wrap a value as an agent tool result with JSON text content.
3
- * Replaces the SDK's jsonResult which was removed from public subpath exports
4
- * in OpenClaw 2026.3.24.
5
- */
6
- export function jsonResult(payload: unknown): { content: Array<{ type: "text"; text: string }>; details: unknown } {
7
- return {
8
- content: [{ type: "text" as const, text: JSON.stringify(payload, null, 2) }],
9
- details: payload,
10
- };
11
- }
package/src/tools-md.ts DELETED
@@ -1,7 +0,0 @@
1
- import fs from "node:fs";
2
-
3
- const TOOLS_MD_PATH = new URL("../TOOLS.md", import.meta.url);
4
-
5
- export function loadToolsMarkdown(): string {
6
- return fs.readFileSync(TOOLS_MD_PATH, "utf8").trim();
7
- }
package/src/wiki-tools.ts DELETED
@@ -1,361 +0,0 @@
1
- import { Type } from "@sinclair/typebox";
2
- import type { OpenClawPluginApi, AnyAgentTool } from "openclaw/plugin-sdk";
3
- import { readNumberParam, readStringParam } from "openclaw/plugin-sdk/param-readers";
4
- import { jsonResult } from "./tool-helpers.js";
5
- import { PondClient } from "@pnds/sdk";
6
- import {
7
- getWikiBlob,
8
- getWikiChangesetDiff,
9
- getWikiOutline,
10
- queryWiki,
11
- getWikiSection,
12
- getWikiTree,
13
- getWikiWorkspaceDiff,
14
- getWikiWorkspaceStatus,
15
- listWikis,
16
- proposeWikiChangeset,
17
- searchWiki,
18
- type PondContext,
19
- } from "@pnds/cli/wiki";
20
- import {
21
- getActiveRunId,
22
- getAllPondAccountStates,
23
- getDispatchMessageId,
24
- getPondAccountState,
25
- getSessionChatId,
26
- getSessionMessageId,
27
- } from "./runtime.js";
28
- import { extractAccountIdFromSessionKey } from "./session.js";
29
-
30
- const WikiTreeSchema = Type.Object({
31
- wiki: Type.String({ minLength: 1, description: "Wiki slug or ID." }),
32
- path: Type.Optional(Type.String({ minLength: 1, description: "Tree path prefix." })),
33
- ref: Type.Optional(Type.String({ minLength: 1, description: "Git ref to inspect." })),
34
- });
35
-
36
- const WikiBlobSchema = Type.Object({
37
- wiki: Type.String({ minLength: 1, description: "Wiki slug or ID." }),
38
- path: Type.String({ minLength: 1, description: "File path inside the wiki." }),
39
- ref: Type.Optional(Type.String({ minLength: 1, description: "Git ref to inspect." })),
40
- });
41
-
42
- const WikiStatusSchema = Type.Object({
43
- wiki: Type.String({ minLength: 1, description: "Wiki slug or ID." }),
44
- });
45
-
46
- const WikiDiffSchema = Type.Object({
47
- wiki: Type.String({ minLength: 1, description: "Wiki slug or ID." }),
48
- });
49
-
50
- const WikiSearchSchema = Type.Object({
51
- wiki: Type.String({ minLength: 1, description: "Wiki slug or ID." }),
52
- query: Type.String({ minLength: 1, description: "Search query." }),
53
- ref: Type.Optional(Type.String({ minLength: 1, description: "Git ref to inspect." })),
54
- pathPrefix: Type.Optional(Type.String({ minLength: 1, description: "Restrict search to a path prefix." })),
55
- limit: Type.Optional(Type.Number({ minimum: 1, description: "Maximum number of results." })),
56
- includeContent: Type.Optional(Type.Boolean({ description: "Include full section content in each result." })),
57
- });
58
-
59
- const WikiQuerySchema = Type.Object({
60
- wiki: Type.String({ minLength: 1, description: "Wiki slug or ID." }),
61
- query: Type.String({ minLength: 1, description: "Natural-language query." }),
62
- ref: Type.Optional(Type.String({ minLength: 1, description: "Git ref to inspect." })),
63
- pathPrefix: Type.Optional(Type.String({ minLength: 1, description: "Restrict the query to a path prefix." })),
64
- limit: Type.Optional(Type.Number({ minimum: 1, description: "Maximum number of section results." })),
65
- includeContent: Type.Optional(Type.Boolean({ description: "Include full section content in each result." })),
66
- });
67
-
68
- const WikiOutlineSchema = Type.Object({
69
- wiki: Type.String({ minLength: 1, description: "Wiki slug or ID." }),
70
- ref: Type.Optional(Type.String({ minLength: 1, description: "Git ref to inspect." })),
71
- pathPrefix: Type.Optional(Type.String({ minLength: 1, description: "Restrict outline to a path prefix." })),
72
- });
73
-
74
- const WikiSectionSchema = Type.Object({
75
- wiki: Type.String({ minLength: 1, description: "Wiki slug or ID." }),
76
- nodeId: Type.String({ minLength: 1, description: "Section node ID returned by wiki_search." }),
77
- ref: Type.Optional(Type.String({ minLength: 1, description: "Git ref to inspect." })),
78
- });
79
-
80
- const WikiChangesetDiffSchema = Type.Object({
81
- wiki: Type.String({ minLength: 1, description: "Wiki slug or ID." }),
82
- changesetId: Type.String({ minLength: 1, description: "Wiki changeset ID." }),
83
- });
84
-
85
- const WikiProposeSchema = Type.Object({
86
- wiki: Type.String({ minLength: 1, description: "Wiki slug or ID." }),
87
- title: Type.String({ minLength: 1, description: "Changeset title." }),
88
- summary: Type.Optional(Type.String({ description: "Optional changeset summary." })),
89
- changesetId: Type.Optional(Type.String({ minLength: 1, description: "Existing changeset ID to update." })),
90
- });
91
-
92
- type ToolSessionContext = {
93
- sessionKey?: string;
94
- agentAccountId?: string;
95
- agentId?: string;
96
- };
97
-
98
- function resolvePondContext(ctx: ToolSessionContext): PondContext {
99
- const accountId = ctx.agentAccountId ?? extractAccountIdFromSessionKey(ctx.sessionKey);
100
- if (accountId) {
101
- const state = getPondAccountState(accountId);
102
- if (state) {
103
- return {
104
- client: state.client,
105
- orgId: state.orgId,
106
- agentId: ctx.agentId ?? process.env.POND_AGENT_ID,
107
- mountRoot: state.wikiMountRoot,
108
- };
109
- }
110
- }
111
-
112
- const states = Array.from(getAllPondAccountStates().values());
113
- if (states.length === 1) {
114
- return {
115
- client: states[0].client,
116
- orgId: states[0].orgId,
117
- agentId: ctx.agentId ?? process.env.POND_AGENT_ID,
118
- mountRoot: states[0].wikiMountRoot,
119
- };
120
- }
121
-
122
- const baseUrl = process.env.POND_API_URL?.trim();
123
- const token = process.env.POND_API_KEY?.trim();
124
- const orgId = process.env.POND_ORG_ID?.trim();
125
- if (baseUrl && token && orgId) {
126
- return {
127
- client: new PondClient({ baseUrl, token }),
128
- orgId,
129
- agentId: ctx.agentId ?? process.env.POND_AGENT_ID,
130
- };
131
- }
132
-
133
- throw new Error(
134
- "Pond wiki tools are unavailable: missing live Pond runtime state and POND_API_URL/POND_API_KEY/POND_ORG_ID.",
135
- );
136
- }
137
-
138
- function formatWikiBlobResult(result: Awaited<ReturnType<typeof getWikiBlob>>) {
139
- return {
140
- wiki: {
141
- id: result.wiki.id,
142
- slug: result.wiki.slug,
143
- name: result.wiki.name,
144
- },
145
- content: result.content,
146
- size: result.size,
147
- };
148
- }
149
-
150
- async function resolveWorkflowContext(ctx: ToolSessionContext) {
151
- if (!ctx.sessionKey) {
152
- return {};
153
- }
154
- const accountId = ctx.agentAccountId ?? extractAccountIdFromSessionKey(ctx.sessionKey);
155
- const chatId = getSessionChatId(ctx.sessionKey);
156
- const state = accountId ? getPondAccountState(accountId) : undefined;
157
- const runId = state ? await getActiveRunId(state, ctx.sessionKey) : undefined;
158
-
159
- // Issue 1: source_chat_id is a FK to the chats table — only set it for actual chat IDs.
160
- // Task dispatches store a tsk_* ID here which would cause a DB FK violation.
161
- const sourceChatId = chatId?.startsWith("cht_") ? chatId : undefined;
162
-
163
- // Issue 2: prefer dispatch-time snapshot over the mutable session map to avoid
164
- // reading a stale message ID when a new inbound message arrives mid-dispatch.
165
- const sourceMessageId =
166
- getDispatchMessageId(ctx.sessionKey) ?? getSessionMessageId(ctx.sessionKey);
167
-
168
- return {
169
- sourceChatId,
170
- sourceMessageId,
171
- sourceRunId: runId,
172
- };
173
- }
174
-
175
- function createWikiTools(toolCtx: ToolSessionContext): AnyAgentTool[] {
176
- const pondContext = () => resolvePondContext(toolCtx);
177
- const workflowContext = () => resolveWorkflowContext(toolCtx);
178
- return [
179
- {
180
- label: "Wiki List",
181
- name: "wiki_list",
182
- description: "List all Pond wikis in the current organization.",
183
- parameters: Type.Object({}),
184
- execute: async () => {
185
- return jsonResult(await listWikis(pondContext()));
186
- },
187
- },
188
- {
189
- label: "Wiki Status",
190
- name: "wiki_status",
191
- description: "Inspect the local mounted wiki workspace before proposing changes.",
192
- parameters: WikiStatusSchema,
193
- execute: async (_toolCallId, params) => {
194
- return jsonResult(
195
- await getWikiWorkspaceStatus(pondContext(), readStringParam(params, "wiki", { required: true })),
196
- );
197
- },
198
- },
199
- {
200
- label: "Wiki Diff",
201
- name: "wiki_diff",
202
- description: "Read the current unified diff for the local mounted wiki workspace.",
203
- parameters: WikiDiffSchema,
204
- execute: async (_toolCallId, params) => {
205
- return jsonResult(
206
- await getWikiWorkspaceDiff(pondContext(), readStringParam(params, "wiki", { required: true })),
207
- );
208
- },
209
- },
210
- {
211
- label: "Wiki Tree",
212
- name: "wiki_tree",
213
- description: "List files and directories under a Pond wiki path.",
214
- parameters: WikiTreeSchema,
215
- execute: async (_toolCallId, params) => {
216
- return jsonResult(
217
- await getWikiTree(pondContext(), readStringParam(params, "wiki", { required: true }), {
218
- path: readStringParam(params, "path"),
219
- }),
220
- );
221
- },
222
- },
223
- {
224
- label: "Wiki Blob",
225
- name: "wiki_blob",
226
- description: "Read a Pond wiki file. Use after wiki_search or wiki_tree narrows the target.",
227
- parameters: WikiBlobSchema,
228
- execute: async (_toolCallId, params) => {
229
- const result = await getWikiBlob(pondContext(), readStringParam(params, "wiki", { required: true }), {
230
- path: readStringParam(params, "path", { required: true }),
231
- });
232
- return jsonResult(formatWikiBlobResult(result));
233
- },
234
- },
235
- {
236
- label: "Wiki Search",
237
- name: "wiki_search",
238
- description: "Search Pond wiki sections by heading and content. Prefer this before reading full files.",
239
- parameters: WikiSearchSchema,
240
- execute: async (_toolCallId, params) => {
241
- return jsonResult(
242
- await searchWiki(
243
- pondContext(),
244
- readStringParam(params, "wiki", { required: true }),
245
- readStringParam(params, "query", { required: true }),
246
- {
247
- ref: readStringParam(params, "ref"),
248
- pathPrefix: readStringParam(params, "pathPrefix"),
249
- limit: readNumberParam(params, "limit", { integer: true }),
250
- includeContent: params.includeContent === true,
251
- },
252
- ),
253
- );
254
- },
255
- },
256
- {
257
- label: "Wiki Query",
258
- name: "wiki_query",
259
- description: "Run a tree-aware Pond wiki query that first narrows documents and then selects the best sections.",
260
- parameters: WikiQuerySchema,
261
- execute: async (_toolCallId, params) => {
262
- return jsonResult(
263
- await queryWiki(
264
- pondContext(),
265
- readStringParam(params, "wiki", { required: true }),
266
- readStringParam(params, "query", { required: true }),
267
- {
268
- ref: readStringParam(params, "ref"),
269
- pathPrefix: readStringParam(params, "pathPrefix"),
270
- limit: readNumberParam(params, "limit", { integer: true }),
271
- includeContent: params.includeContent === true,
272
- },
273
- ),
274
- );
275
- },
276
- },
277
- {
278
- label: "Wiki Outline",
279
- name: "wiki_outline",
280
- description: "Read the structural outline of a Pond wiki or a specific path prefix before drilling into sections.",
281
- parameters: WikiOutlineSchema,
282
- execute: async (_toolCallId, params) => {
283
- return jsonResult(
284
- await getWikiOutline(pondContext(), readStringParam(params, "wiki", { required: true }), {
285
- ref: readStringParam(params, "ref"),
286
- pathPrefix: readStringParam(params, "pathPrefix"),
287
- }),
288
- );
289
- },
290
- },
291
- {
292
- label: "Wiki Section",
293
- name: "wiki_section",
294
- description: "Read the full body of a Pond wiki section identified by nodeId from wiki_search.",
295
- parameters: WikiSectionSchema,
296
- execute: async (_toolCallId, params) => {
297
- return jsonResult(
298
- await getWikiSection(pondContext(), readStringParam(params, "wiki", { required: true }), {
299
- nodeId: readStringParam(params, "nodeId", { required: true }),
300
- ref: readStringParam(params, "ref"),
301
- }),
302
- );
303
- },
304
- },
305
- {
306
- label: "Wiki Changeset Diff",
307
- name: "wiki_changeset_diff",
308
- description: "Fetch the unified diff and file summary for a Pond wiki changeset.",
309
- parameters: WikiChangesetDiffSchema,
310
- execute: async (_toolCallId, params) => {
311
- return jsonResult(
312
- await getWikiChangesetDiff(
313
- pondContext(),
314
- readStringParam(params, "wiki", { required: true }),
315
- readStringParam(params, "changesetId", { required: true }),
316
- ),
317
- );
318
- },
319
- },
320
- {
321
- label: "Wiki Propose",
322
- name: "wiki_propose",
323
- description: "Create or update a proposed wiki changeset from the current local mounted workspace.",
324
- parameters: WikiProposeSchema,
325
- execute: async (_toolCallId, params) => {
326
- const workflow = await workflowContext();
327
- return jsonResult(
328
- await proposeWikiChangeset(pondContext(), readStringParam(params, "wiki", { required: true }), {
329
- title: readStringParam(params, "title", { required: true }),
330
- summary: readStringParam(params, "summary"),
331
- changesetId: readStringParam(params, "changesetId"),
332
- sourceChatId: workflow.sourceChatId,
333
- sourceMessageId: workflow.sourceMessageId,
334
- sourceRunId: workflow.sourceRunId,
335
- }),
336
- );
337
- },
338
- },
339
- ];
340
- }
341
-
342
- export function registerPondWikiTools(api: OpenClawPluginApi) {
343
- api.registerTool(
344
- (ctx) => createWikiTools(ctx),
345
- {
346
- names: [
347
- "wiki_list",
348
- "wiki_status",
349
- "wiki_diff",
350
- "wiki_tree",
351
- "wiki_blob",
352
- "wiki_search",
353
- "wiki_query",
354
- "wiki_outline",
355
- "wiki_section",
356
- "wiki_changeset_diff",
357
- "wiki_propose",
358
- ],
359
- },
360
- );
361
- }