@pnds/pond 1.1.0 → 1.2.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/TOOLS.md +54 -0
- package/package.json +17 -4
- package/src/action-tools.ts +223 -0
- package/src/channel.ts +2 -2
- package/src/fork.ts +213 -0
- package/src/gateway.ts +717 -222
- package/src/hooks.ts +30 -25
- package/src/index.ts +5 -1
- package/src/outbound.ts +4 -1
- package/src/routing.ts +48 -0
- package/src/runtime.ts +84 -1
- package/src/session.ts +5 -0
- package/src/tool-helpers.ts +11 -0
- package/src/tools-md.ts +7 -0
- package/src/wiki-helper.ts +157 -0
- package/src/wiki-tools.ts +370 -0
|
@@ -0,0 +1,370 @@
|
|
|
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
|
+
blob: {
|
|
146
|
+
path: result.blob.path,
|
|
147
|
+
ref: result.blob.ref,
|
|
148
|
+
size: result.blob.size,
|
|
149
|
+
mime_type: result.blob.mime_type,
|
|
150
|
+
is_binary: result.blob.is_binary,
|
|
151
|
+
encoding: result.blob.encoding,
|
|
152
|
+
},
|
|
153
|
+
text: result.text,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function resolveWorkflowContext(ctx: ToolSessionContext) {
|
|
158
|
+
if (!ctx.sessionKey) {
|
|
159
|
+
return {};
|
|
160
|
+
}
|
|
161
|
+
const accountId = ctx.agentAccountId ?? extractAccountIdFromSessionKey(ctx.sessionKey);
|
|
162
|
+
const chatId = getSessionChatId(ctx.sessionKey);
|
|
163
|
+
const state = accountId ? getPondAccountState(accountId) : undefined;
|
|
164
|
+
const runId = state ? await getActiveRunId(state, ctx.sessionKey) : undefined;
|
|
165
|
+
|
|
166
|
+
// Issue 1: source_chat_id is a FK to the chats table — only set it for actual chat IDs.
|
|
167
|
+
// Task dispatches store a tsk_* ID here which would cause a DB FK violation.
|
|
168
|
+
const sourceChatId = chatId?.startsWith("cht_") ? chatId : undefined;
|
|
169
|
+
|
|
170
|
+
// Issue 2: prefer dispatch-time snapshot over the mutable session map to avoid
|
|
171
|
+
// reading a stale message ID when a new inbound message arrives mid-dispatch.
|
|
172
|
+
const sourceMessageId =
|
|
173
|
+
getDispatchMessageId(ctx.sessionKey) ?? getSessionMessageId(ctx.sessionKey);
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
sourceChatId,
|
|
177
|
+
sourceMessageId,
|
|
178
|
+
sourceRunId: runId,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function createWikiTools(toolCtx: ToolSessionContext): AnyAgentTool[] {
|
|
183
|
+
const pondContext = () => resolvePondContext(toolCtx);
|
|
184
|
+
const workflowContext = () => resolveWorkflowContext(toolCtx);
|
|
185
|
+
return [
|
|
186
|
+
{
|
|
187
|
+
label: "Wiki List",
|
|
188
|
+
name: "wiki_list",
|
|
189
|
+
description: "List all Pond wikis in the current organization.",
|
|
190
|
+
parameters: Type.Object({}),
|
|
191
|
+
execute: async () => {
|
|
192
|
+
return jsonResult(await listWikis(pondContext()));
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
label: "Wiki Status",
|
|
197
|
+
name: "wiki_status",
|
|
198
|
+
description: "Inspect the local mounted wiki workspace before proposing changes.",
|
|
199
|
+
parameters: WikiStatusSchema,
|
|
200
|
+
execute: async (_toolCallId, params) => {
|
|
201
|
+
return jsonResult(
|
|
202
|
+
await getWikiWorkspaceStatus(pondContext(), readStringParam(params, "wiki", { required: true })),
|
|
203
|
+
);
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
label: "Wiki Diff",
|
|
208
|
+
name: "wiki_diff",
|
|
209
|
+
description: "Read the current unified diff for the local mounted wiki workspace.",
|
|
210
|
+
parameters: WikiDiffSchema,
|
|
211
|
+
execute: async (_toolCallId, params) => {
|
|
212
|
+
return jsonResult(
|
|
213
|
+
await getWikiWorkspaceDiff(pondContext(), readStringParam(params, "wiki", { required: true })),
|
|
214
|
+
);
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
label: "Wiki Tree",
|
|
219
|
+
name: "wiki_tree",
|
|
220
|
+
description: "List files and directories under a Pond wiki path.",
|
|
221
|
+
parameters: WikiTreeSchema,
|
|
222
|
+
execute: async (_toolCallId, params) => {
|
|
223
|
+
return jsonResult(
|
|
224
|
+
await getWikiTree(pondContext(), readStringParam(params, "wiki", { required: true }), {
|
|
225
|
+
path: readStringParam(params, "path"),
|
|
226
|
+
ref: readStringParam(params, "ref"),
|
|
227
|
+
}),
|
|
228
|
+
);
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
label: "Wiki Blob",
|
|
233
|
+
name: "wiki_blob",
|
|
234
|
+
description: "Read a Pond wiki file. Use after wiki_search or wiki_tree narrows the target.",
|
|
235
|
+
parameters: WikiBlobSchema,
|
|
236
|
+
execute: async (_toolCallId, params) => {
|
|
237
|
+
const result = await getWikiBlob(pondContext(), readStringParam(params, "wiki", { required: true }), {
|
|
238
|
+
path: readStringParam(params, "path", { required: true }),
|
|
239
|
+
ref: readStringParam(params, "ref"),
|
|
240
|
+
});
|
|
241
|
+
return jsonResult(formatWikiBlobResult(result));
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
label: "Wiki Search",
|
|
246
|
+
name: "wiki_search",
|
|
247
|
+
description: "Search Pond wiki sections by heading and content. Prefer this before reading full files.",
|
|
248
|
+
parameters: WikiSearchSchema,
|
|
249
|
+
execute: async (_toolCallId, params) => {
|
|
250
|
+
return jsonResult(
|
|
251
|
+
await searchWiki(
|
|
252
|
+
pondContext(),
|
|
253
|
+
readStringParam(params, "wiki", { required: true }),
|
|
254
|
+
readStringParam(params, "query", { required: true }),
|
|
255
|
+
{
|
|
256
|
+
ref: readStringParam(params, "ref"),
|
|
257
|
+
pathPrefix: readStringParam(params, "pathPrefix"),
|
|
258
|
+
limit: readNumberParam(params, "limit", { integer: true }),
|
|
259
|
+
includeContent: params.includeContent === true,
|
|
260
|
+
},
|
|
261
|
+
),
|
|
262
|
+
);
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
label: "Wiki Query",
|
|
267
|
+
name: "wiki_query",
|
|
268
|
+
description: "Run a tree-aware Pond wiki query that first narrows documents and then selects the best sections.",
|
|
269
|
+
parameters: WikiQuerySchema,
|
|
270
|
+
execute: async (_toolCallId, params) => {
|
|
271
|
+
return jsonResult(
|
|
272
|
+
await queryWiki(
|
|
273
|
+
pondContext(),
|
|
274
|
+
readStringParam(params, "wiki", { required: true }),
|
|
275
|
+
readStringParam(params, "query", { required: true }),
|
|
276
|
+
{
|
|
277
|
+
ref: readStringParam(params, "ref"),
|
|
278
|
+
pathPrefix: readStringParam(params, "pathPrefix"),
|
|
279
|
+
limit: readNumberParam(params, "limit", { integer: true }),
|
|
280
|
+
includeContent: params.includeContent === true,
|
|
281
|
+
},
|
|
282
|
+
),
|
|
283
|
+
);
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
label: "Wiki Outline",
|
|
288
|
+
name: "wiki_outline",
|
|
289
|
+
description: "Read the structural outline of a Pond wiki or a specific path prefix before drilling into sections.",
|
|
290
|
+
parameters: WikiOutlineSchema,
|
|
291
|
+
execute: async (_toolCallId, params) => {
|
|
292
|
+
return jsonResult(
|
|
293
|
+
await getWikiOutline(pondContext(), readStringParam(params, "wiki", { required: true }), {
|
|
294
|
+
ref: readStringParam(params, "ref"),
|
|
295
|
+
pathPrefix: readStringParam(params, "pathPrefix"),
|
|
296
|
+
}),
|
|
297
|
+
);
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
label: "Wiki Section",
|
|
302
|
+
name: "wiki_section",
|
|
303
|
+
description: "Read the full body of a Pond wiki section identified by nodeId from wiki_search.",
|
|
304
|
+
parameters: WikiSectionSchema,
|
|
305
|
+
execute: async (_toolCallId, params) => {
|
|
306
|
+
return jsonResult(
|
|
307
|
+
await getWikiSection(pondContext(), readStringParam(params, "wiki", { required: true }), {
|
|
308
|
+
nodeId: readStringParam(params, "nodeId", { required: true }),
|
|
309
|
+
ref: readStringParam(params, "ref"),
|
|
310
|
+
}),
|
|
311
|
+
);
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
label: "Wiki Changeset Diff",
|
|
316
|
+
name: "wiki_changeset_diff",
|
|
317
|
+
description: "Fetch the unified diff and file summary for a Pond wiki changeset.",
|
|
318
|
+
parameters: WikiChangesetDiffSchema,
|
|
319
|
+
execute: async (_toolCallId, params) => {
|
|
320
|
+
return jsonResult(
|
|
321
|
+
await getWikiChangesetDiff(
|
|
322
|
+
pondContext(),
|
|
323
|
+
readStringParam(params, "wiki", { required: true }),
|
|
324
|
+
readStringParam(params, "changesetId", { required: true }),
|
|
325
|
+
),
|
|
326
|
+
);
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
label: "Wiki Propose",
|
|
331
|
+
name: "wiki_propose",
|
|
332
|
+
description: "Create or update a proposed wiki changeset from the current local mounted workspace.",
|
|
333
|
+
parameters: WikiProposeSchema,
|
|
334
|
+
execute: async (_toolCallId, params) => {
|
|
335
|
+
const workflow = await workflowContext();
|
|
336
|
+
return jsonResult(
|
|
337
|
+
await proposeWikiChangeset(pondContext(), readStringParam(params, "wiki", { required: true }), {
|
|
338
|
+
title: readStringParam(params, "title", { required: true }),
|
|
339
|
+
summary: readStringParam(params, "summary"),
|
|
340
|
+
changesetId: readStringParam(params, "changesetId"),
|
|
341
|
+
sourceChatId: workflow.sourceChatId,
|
|
342
|
+
sourceMessageId: workflow.sourceMessageId,
|
|
343
|
+
sourceRunId: workflow.sourceRunId,
|
|
344
|
+
}),
|
|
345
|
+
);
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
];
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export function registerPondWikiTools(api: OpenClawPluginApi) {
|
|
352
|
+
api.registerTool(
|
|
353
|
+
(ctx) => createWikiTools(ctx),
|
|
354
|
+
{
|
|
355
|
+
names: [
|
|
356
|
+
"wiki_list",
|
|
357
|
+
"wiki_status",
|
|
358
|
+
"wiki_diff",
|
|
359
|
+
"wiki_tree",
|
|
360
|
+
"wiki_blob",
|
|
361
|
+
"wiki_search",
|
|
362
|
+
"wiki_query",
|
|
363
|
+
"wiki_outline",
|
|
364
|
+
"wiki_section",
|
|
365
|
+
"wiki_changeset_diff",
|
|
366
|
+
"wiki_propose",
|
|
367
|
+
],
|
|
368
|
+
},
|
|
369
|
+
);
|
|
370
|
+
}
|