@owlmeans/bot-agent 0.1.18-rc.2 → 0.1.18
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/README.md +23 -15
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/bot-agent/SKILL.md +48 -33
- package/build/helpers/agent/types.d.ts +8 -7
- package/build/helpers/agent/types.d.ts.map +1 -1
- package/build/helpers/agent.d.ts.map +1 -1
- package/build/helpers/agent.js +9 -34
- package/build/helpers/agent.js.map +1 -1
- package/build/plugin.d.ts +3 -2
- package/build/plugin.d.ts.map +1 -1
- package/build/plugin.js +3 -2
- package/build/plugin.js.map +1 -1
- package/build/tools.d.ts +2 -1
- package/build/tools.d.ts.map +1 -1
- package/build/tools.js +3 -3
- package/build/tools.js.map +1 -1
- package/build/types.d.ts +11 -2
- package/build/types.d.ts.map +1 -1
- package/package.json +12 -12
- package/src/helpers/agent/types.ts +8 -7
- package/src/helpers/agent.ts +9 -38
- package/src/plugin.ts +3 -2
- package/src/tools.ts +3 -3
- package/src/types.ts +11 -2
- package/tests/context.ts +20 -16
- package/tests/helper.spec.ts +12 -14
- package/tests/loop.spec.ts +16 -10
- package/tests/plugin.spec.ts +20 -16
- package/tests/tools.spec.ts +13 -14
package/README.md
CHANGED
|
@@ -2,17 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
The bridge between an OwlMeans chat bot and an `@owlmeans/agent` agent. `botAgentPlugin` hands the
|
|
4
4
|
agent ONE bot update: the message — platform, chat, sender, the text, the replied-to text — and the
|
|
5
|
-
|
|
6
|
-
(`reply`, `delete`, `warn`, `mute`, `ban`, `react`)
|
|
7
|
-
|
|
5
|
+
instructions the handler passes (those of the application's own keyword groups the message matched)
|
|
6
|
+
become the agent's context; the bot's actions (`reply`, `delete`, `warn`, `mute`, `ban`, `react`)
|
|
7
|
+
become its tools, limited to what the platform supports and the caller allows, each recording the
|
|
8
|
+
handler's `ref`. A tool never throws: it answers `done`, or what went wrong.
|
|
8
9
|
|
|
9
|
-
It stores, receives and sends nothing itself — the
|
|
10
|
-
`BotActions`, which record every
|
|
10
|
+
It stores, receives, matches and sends nothing itself — the handler matches the message against the
|
|
11
|
+
application's records, and the actions are `@owlmeans/server-bot`'s `BotActions`, which record every
|
|
12
|
+
call as activity.
|
|
11
13
|
|
|
12
14
|
## Installation
|
|
13
15
|
|
|
14
16
|
```bash
|
|
15
|
-
bun add @owlmeans/bot-agent@^0.1.18
|
|
17
|
+
bun add @owlmeans/bot-agent@^0.1.18
|
|
16
18
|
```
|
|
17
19
|
|
|
18
20
|
`@owlmeans/agent` and `@langchain/core` are peers.
|
|
@@ -20,14 +22,20 @@ bun add @owlmeans/bot-agent@^0.1.18-rc.2
|
|
|
20
22
|
## Usage
|
|
21
23
|
|
|
22
24
|
```typescript
|
|
23
|
-
import { BotActionKind } from '@owlmeans/bot'
|
|
25
|
+
import { BotActionKind, botTextHelper } from '@owlmeans/bot'
|
|
24
26
|
import { botAgentHelper, botAgentPlugin } from '@owlmeans/bot-agent'
|
|
25
27
|
import { botHandler } from '@owlmeans/server-bot'
|
|
26
28
|
|
|
27
|
-
export const moderate = botHandler('moderate', async (update, bot) => {
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
29
|
+
export const moderate = botHandler('moderate', async (update, bot, ctx) => {
|
|
30
|
+
const text = update.message?.text ?? ''
|
|
31
|
+
const groups = (await keywordGroupsOf(ctx, update.scope)) // the application's own records
|
|
32
|
+
.filter(group => botTextHelper.matched(text, group.keywords).length > 0)
|
|
33
|
+
if (groups.length === 0) return
|
|
34
|
+
const agent = makeModeratorAgent() // your makeAgentModel(…) factory
|
|
35
|
+
agent.use(botAgentPlugin({
|
|
36
|
+
update, bot, ref: groups[0].id, instruction: groups.map(group => group.instruction ?? ''),
|
|
37
|
+
allow: [BotActionKind.Delete, BotActionKind.Reply],
|
|
38
|
+
}))
|
|
31
39
|
await agent.invoke(botAgentHelper.promptOf(update), { action: 'moderate' })
|
|
32
40
|
})
|
|
33
41
|
```
|
|
@@ -36,9 +44,9 @@ export const moderate = botHandler('moderate', async (update, bot) => {
|
|
|
36
44
|
|
|
37
45
|
| Export | What it is |
|
|
38
46
|
|---|---|
|
|
39
|
-
| `botAgentPlugin({ update, bot, allow?, instruction? })` | The `AgentPlugin`: the update as context, the actions as tools; alias `BOT_AGENT_PLUGIN` |
|
|
40
|
-
| `botAgentTools({ update, bot, allow? })` | The same tools without the plugin, as an `AgentToolSet` |
|
|
41
|
-
| `botAgentHelper` | `instructionOf(
|
|
47
|
+
| `botAgentPlugin({ update, bot, allow?, ref?, instruction? })` | The `AgentPlugin`: the update and the instructions (`string \| string[]`) as context, the actions as tools; alias `BOT_AGENT_PLUGIN` |
|
|
48
|
+
| `botAgentTools({ update, bot, allow?, ref? })` | The same tools without the plugin, as an `AgentToolSet` |
|
|
49
|
+
| `botAgentHelper` | `instructionOf(instruction?)`, `contextOf(update, instruction?)`, `promptOf(update)` |
|
|
42
50
|
| `BotAgentTool`, `BOT_AGENT_TOOL_ACTIONS`, `BOT_AGENT_ACTIONS`, `BOT_AGENT_DONE`, `BOT_AGENT_ORDER` | Tool names, the action each performs, the default `allow`, the success answer, the plugin order |
|
|
43
51
|
| `BotAgentOptions`, `BotAgentToolsOptions`, `BotAgentHelper` | Types |
|
|
44
52
|
|
|
@@ -55,7 +63,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
55
63
|
your project's skill store (`.agents/skills/`):
|
|
56
64
|
|
|
57
65
|
```sh
|
|
58
|
-
npx @owlmeans/agent-skills@^0.1.18
|
|
66
|
+
npx @owlmeans/agent-skills@^0.1.18
|
|
59
67
|
```
|
|
60
68
|
|
|
61
69
|
The embedded files are version-matched to this package release. Do not edit them
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 3,
|
|
3
3
|
"package": "@owlmeans/bot-agent",
|
|
4
|
-
"version": "0.1.18
|
|
5
|
-
"generatedAt": "2026-10-
|
|
4
|
+
"version": "0.1.18",
|
|
5
|
+
"generatedAt": "2026-10-28T00:31:34.761Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: bot-agent
|
|
3
|
-
description: How to use @owlmeans/bot-agent — the bridge that lets an @owlmeans/agent agent act on ONE chat-bot update. botAgentPlugin contributes the message (platform, chat, sender, text, replied-to text) and the
|
|
3
|
+
description: How to use @owlmeans/bot-agent — the bridge that lets an @owlmeans/agent agent act on ONE chat-bot update. botAgentPlugin contributes the message (platform, chat, sender, text, replied-to text) and the instructions the handler passes (those of the application's own keyword groups the message matched) as context, and the bot's actions (reply, delete, warn, mute, ban, react) as never-throwing tools limited to what the platform supports and allow names, each recording the handler's ref; botAgentTools gives the tools without the plugin; botAgentHelper turns instructions and an update into instruction text, context and the opening turn. Auto-invoked when an AI bot's handler hands a message to an agent, when writing a target's bot agent module, or when importing botAgentPlugin.
|
|
4
4
|
user-invocable: false
|
|
5
5
|
---
|
|
6
6
|
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
@@ -8,21 +8,23 @@ user-invocable: false
|
|
|
8
8
|
# @owlmeans/bot-agent
|
|
9
9
|
|
|
10
10
|
**Layer:** Server (its tools act through `@owlmeans/server-bot`'s `BotActions`; type-only edge)
|
|
11
|
-
**Install:** `"@owlmeans/bot-agent": "^0.1.18
|
|
11
|
+
**Install:** `"@owlmeans/bot-agent": "^0.1.18"` in `dependencies`, plus the `@owlmeans/agent` and
|
|
12
12
|
`@langchain/core` **peers**
|
|
13
|
-
**Contract:** `@owlmeans/bot` (updates,
|
|
13
|
+
**Contract:** `@owlmeans/bot` (updates, action kinds, the text helper); actions: `@owlmeans/server-bot`
|
|
14
14
|
|
|
15
15
|
An AI bot's handler receives an update and the connection's `BotActions`. This package turns the two
|
|
16
|
-
into what an agent needs: the update as CONTEXT, the actions as TOOLS. It
|
|
17
|
-
|
|
16
|
+
into what an agent needs: the update and the instructions as CONTEXT, the actions as TOOLS. It
|
|
17
|
+
stores, receives, matches and sends nothing itself — the handler matches the message against the
|
|
18
|
+
application's own keyword groups and passes their instructions; every tool calls a `BotActions`
|
|
19
|
+
method, which records one activity whatever happens.
|
|
18
20
|
|
|
19
21
|
## Key exports
|
|
20
22
|
|
|
21
23
|
| Export | What it is |
|
|
22
24
|
|---|---|
|
|
23
|
-
| `botAgentPlugin({ update, bot, allow?, instruction? })` → `AgentPlugin` | The update as context, the actions as tools; alias `BOT_AGENT_PLUGIN` (`'bot-agent'`), order `BOT_AGENT_ORDER` (30) |
|
|
24
|
-
| `botAgentTools({ update, bot, allow? })` → `AgentToolSet` | The same tools without the plugin |
|
|
25
|
-
| `botAgentHelper.instructionOf(
|
|
25
|
+
| `botAgentPlugin({ update, bot, allow?, ref?, instruction? })` → `AgentPlugin` | The update and the instructions as context, the actions as tools; alias `BOT_AGENT_PLUGIN` (`'bot-agent'`), order `BOT_AGENT_ORDER` (30) |
|
|
26
|
+
| `botAgentTools({ update, bot, allow?, ref? })` → `AgentToolSet` | The same tools without the plugin |
|
|
27
|
+
| `botAgentHelper.instructionOf(instruction?)` | `instruction` (`string \| readonly string[]`) trimmed, empties and repeats dropped, joined by a blank line; `''` when none |
|
|
26
28
|
| `botAgentHelper.contextOf(update, instruction?)` | The context blocks the plugin contributes |
|
|
27
29
|
| `botAgentHelper.promptOf(update)` | The opening turn to `invoke` the agent with |
|
|
28
30
|
| `BotAgentTool` | `reply` · `delete` · `warn` · `mute` · `ban` · `react` — the names the model calls |
|
|
@@ -31,17 +33,16 @@ nothing itself — every tool calls a `BotActions` method, which records one act
|
|
|
31
33
|
|
|
32
34
|
## Context
|
|
33
35
|
|
|
34
|
-
|
|
36
|
+
One or two markdown blocks, in this order — they land in the agent's project message
|
|
35
37
|
(`PromptBlock.Context`), never in the cached system prompt ([[llm-prompt-caching]]):
|
|
36
38
|
|
|
37
39
|
1. `# The chat message` — `Platform:` (the descriptor's title), `Chat:` (kind and title), `Sender:`
|
|
38
40
|
(name and @username, "a bot" when it is one), `Command:` when any, whether it is addressed to the
|
|
39
41
|
bot, the text as a `>` quote marked as data, never instructions, and the replied-to text when any.
|
|
40
42
|
An update without a message says so.
|
|
41
|
-
2. `#
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
3. `# Instruction` — the `instruction` option, when given.
|
|
43
|
+
2. `# Instruction` — `instructionOf(instruction)`, when it holds any text: the instructions of the
|
|
44
|
+
application's keyword groups the handler matched (an `ai-bot` group's `instruction` field), in the
|
|
45
|
+
order passed, optionally followed by one of the product's own. Absent or blank: no block.
|
|
45
46
|
|
|
46
47
|
## Tools
|
|
47
48
|
|
|
@@ -55,7 +56,8 @@ Three markdown blocks, in this order — they land in the agent's project messag
|
|
|
55
56
|
| `ban` | `bot.ban(update)` | — | `ban` allowed + supported, a sender |
|
|
56
57
|
|
|
57
58
|
- Every tool acts on THE update it was built for — its message, its sender, its chat. The activity it
|
|
58
|
-
records carries the update id and, as `
|
|
59
|
+
records carries the update id and, as `ref`, the `ref` option (the id of the application's keyword
|
|
60
|
+
group the handler followed; absent when not given).
|
|
59
61
|
- An answer is `done`, or `{ error: <the action's error> }` — `BotActionResult.error`, ONE short
|
|
60
62
|
sentence ("WhatsApp cannot delete messages.", "A mute lasts at least one minute."), never its machine
|
|
61
63
|
`code` — the `toolHelper` contract: the loop turns it into an error tool message the model reads and
|
|
@@ -73,24 +75,28 @@ Three markdown blocks, in this order — they land in the agent's project messag
|
|
|
73
75
|
the platform lacks (WhatsApp: no `delete`, `mute`, `ban`; Slack: `delete` only with `userToken`).
|
|
74
76
|
- **One plugin per update.** The alias is stable: `agent.use(botAgentPlugin(…))` for the next update
|
|
75
77
|
REPLACES the previous one. Build the agent per update; never share a plugin across updates.
|
|
76
|
-
- **The groups' instructions are the guidance.** The
|
|
77
|
-
|
|
78
|
+
- **The groups' instructions are the guidance.** The handler matches the message against the
|
|
79
|
+
application's own keyword groups (`botTextHelper.matched`) and passes the matched ones'
|
|
80
|
+
`instruction`s; the agent's persona (its `prompt.role`) says what kind of bot it is and never
|
|
81
|
+
restates or overrides what a group instructs.
|
|
78
82
|
- **The tool names are generic** (`reply`, `delete`) — do not give the same agent other tools under
|
|
79
83
|
those names: tool sets merge by key.
|
|
80
84
|
- **The agent's failure never escapes the handler.** A ReAct run is not resumable: a throw makes the
|
|
81
85
|
worker retry the update, which re-runs the agent and repeats the actions it already took. Catch it
|
|
82
86
|
and `bot.note` the failure; a missing model key is noted BEFORE the agent is built.
|
|
83
87
|
- **A ONE-answer bot needs no agent.** A support bot that only writes a reply can ask one model call
|
|
84
|
-
(`makeLlmModel(…).ask`) with `botAgentHelper.
|
|
85
|
-
`
|
|
88
|
+
(`makeLlmModel(…).ask`) with `botAgentHelper.contextOf(update, instructions)`, then `bot.reply` the
|
|
89
|
+
answer with `{ ref }` — no tools, no loop.
|
|
86
90
|
|
|
87
91
|
## Target wiring
|
|
88
92
|
|
|
89
93
|
An `ai-bot` target (docs/bots.md §3.5, §5): the generated agent module
|
|
90
94
|
`sources/backend/src/agents/<alias>.ts` builds the agent from the project's ONE model module
|
|
91
95
|
`lib/llm.ts` (`getModel()`, `llmSpectator`); the generated handler
|
|
92
|
-
`sources/backend/src/bot/handlers/<slug>.ts`
|
|
93
|
-
|
|
96
|
+
`sources/backend/src/bot/handlers/<slug>.ts` loads the application's keyword groups for
|
|
97
|
+
`update.scope` through the backend model, matches the text with `botTextHelper.matched`, checks
|
|
98
|
+
`llmConfigured` and `bot.note`s the refusal, then `.use(botAgentPlugin({ update, bot, ref, instruction }))`
|
|
99
|
+
and invokes the agent with the update.
|
|
94
100
|
|
|
95
101
|
```ts
|
|
96
102
|
// sources/backend/src/agents/moderator.ts
|
|
@@ -98,7 +104,7 @@ import { makeAgentModel, type AgentModel } from '@owlmeans/agent'
|
|
|
98
104
|
import { DEFAULT_EFFORT, makeExecutionService, makePromptService, spectate } from '@owlmeans/llm'
|
|
99
105
|
import { getModel, llmSpectator, ModelUnavailable } from '../lib/llm.js'
|
|
100
106
|
|
|
101
|
-
/** The persona. What to do with a message comes from the groups it matched, never from here. */
|
|
107
|
+
/** The persona. What to do with a message comes from the instructions of the groups it matched, never from here. */
|
|
102
108
|
const ROLE = 'You moderate a Telegram community for its owner. Judge the member\'s message against the '
|
|
103
109
|
+ 'instructions of the keyword groups it matched and act only through your tools: delete what breaks a '
|
|
104
110
|
+ 'rule, warn the sender, mute a repeat offender. Never chat with the member.'
|
|
@@ -124,31 +130,39 @@ export const makeModeratorAgent = (): AgentModel => makeAgentModel({
|
|
|
124
130
|
```ts
|
|
125
131
|
// sources/backend/src/bot/handlers/moderate.ts
|
|
126
132
|
import { compactionHelper } from '@owlmeans/agent'
|
|
127
|
-
import { BotActionKind } from '@owlmeans/bot'
|
|
133
|
+
import { BotActionKind, botTextHelper } from '@owlmeans/bot'
|
|
128
134
|
import { botAgentHelper, botAgentPlugin } from '@owlmeans/bot-agent'
|
|
129
135
|
import { logger } from '@owlmeans/log'
|
|
130
136
|
import { botHandler } from '@owlmeans/server-bot'
|
|
131
137
|
import { makeModeratorAgent } from '../../agents/moderator.js'
|
|
132
138
|
import { llmConfigured, MODEL_UNAVAILABLE } from '../../lib/llm.js'
|
|
139
|
+
import { makeKeywordGroupModel } from '../../models/keyword-group/keyword-group.js' // the app's own records
|
|
133
140
|
|
|
134
141
|
const log = logger('bot:moderate')
|
|
135
142
|
|
|
136
|
-
export const moderate = botHandler('moderate', async (update, bot) => {
|
|
137
|
-
|
|
143
|
+
export const moderate = botHandler('moderate', async (update, bot, ctx) => {
|
|
144
|
+
const text = update.message?.text ?? ''
|
|
145
|
+
const groups = (await makeKeywordGroupModel(ctx).enabledOf(update.scope))
|
|
146
|
+
.filter(group => botTextHelper.matched(text, group.keywords).length > 0)
|
|
147
|
+
if (groups.length === 0) return // only what a keyword group matched
|
|
148
|
+
const ref = groups[0].id // recorded on every activity the agent causes
|
|
138
149
|
if (!llmConfigured) { // no model key: record the refusal, ask nothing
|
|
139
|
-
await bot.note(update, MODEL_UNAVAILABLE)
|
|
150
|
+
await bot.note(update, MODEL_UNAVAILABLE, { ref })
|
|
140
151
|
return
|
|
141
152
|
}
|
|
142
153
|
|
|
143
154
|
const agent = makeModeratorAgent()
|
|
144
|
-
agent.use(botAgentPlugin({
|
|
155
|
+
agent.use(botAgentPlugin({
|
|
156
|
+
update, bot, ref, instruction: groups.map(group => group.instruction ?? ''),
|
|
157
|
+
allow: [BotActionKind.Delete, BotActionKind.Reply, BotActionKind.Mute],
|
|
158
|
+
}))
|
|
145
159
|
try {
|
|
146
160
|
const { message } = await agent.invoke(botAgentHelper.promptOf(update), { action: 'moderate' })
|
|
147
|
-
await bot.note(update, compactionHelper.messageText(message)) // what the agent did, on the activity
|
|
161
|
+
await bot.note(update, compactionHelper.messageText(message), { ref }) // what the agent did, on the activity
|
|
148
162
|
} catch (error) {
|
|
149
163
|
// Never rethrow: a retried update re-runs the agent and repeats the actions it already took.
|
|
150
164
|
log.warn('The moderator failed', error)
|
|
151
|
-
await bot.note(update, 'The moderator could not decide on this message.')
|
|
165
|
+
await bot.note(update, 'The moderator could not decide on this message.', { ref })
|
|
152
166
|
}
|
|
153
167
|
})
|
|
154
168
|
```
|
|
@@ -165,14 +179,15 @@ export const moderate = botHandler('moderate', async (update, bot) => {
|
|
|
165
179
|
Category A, `bun test ./tests`: real `@owlmeans/server-bot` with its built-in plugins (static
|
|
166
180
|
resources, mock Telegram and WhatsApp connections, messages simulated and dispatched by the runtime)
|
|
167
181
|
and a scripted model double (`tests/_tools/model.ts`, as in the agent's
|
|
168
|
-
[testing](../agent/parts/testing.md) part) driving the loop
|
|
169
|
-
|
|
170
|
-
|
|
182
|
+
[testing](../agent/parts/testing.md) part) driving the loop; the specs' handlers match their own
|
|
183
|
+
keyword lists with `botTextHelper` and pass `ref` and `instruction`, as a generated one does. Covered:
|
|
184
|
+
`instructionOf`; the context blocks (with and without instructions); the tool set by `supports` /
|
|
185
|
+
`allow` / the update's shape; each tool's action, update, `ref` and answer; refusals answered as the action's sentence; one alias; a full loop acting through
|
|
171
186
|
the tools and recovering from a refused or unoffered tool.
|
|
172
187
|
|
|
173
188
|
## Depends On
|
|
174
189
|
|
|
175
|
-
- `@owlmeans/bot` — `BotUpdate`, `BotActionKind`, `BOT_PLATFORMS`
|
|
190
|
+
- `@owlmeans/bot` — `BotUpdate`, `BotActionKind`, `BOT_PLATFORMS` (a handler also uses its `botTextHelper`)
|
|
176
191
|
- `@owlmeans/server-bot` — `BotActions`, `BotActionResult` (types only)
|
|
177
192
|
- peers `@owlmeans/agent` (`AgentPlugin`, `toolHelper`), `@langchain/core` (`tool`)
|
|
178
193
|
|
|
@@ -180,5 +195,5 @@ the tools and recovering from a refused or unoffered tool.
|
|
|
180
195
|
|
|
181
196
|
- [[agent]] — the `AgentPlugin` seam, `toolHelper.safeInvokeTool`, `makeAgentModel`
|
|
182
197
|
- [[server-bot]] — handlers, `BotActions` (never throw), the worker runtime
|
|
183
|
-
- [[bot]] — updates,
|
|
198
|
+
- [[bot]] — updates, `botTextHelper`, platform support
|
|
184
199
|
- [[llm]] — `makeExecutionService`, `makePromptService`, `spectate`
|
|
@@ -2,17 +2,18 @@ import type { BotUpdate } from '@owlmeans/bot';
|
|
|
2
2
|
/** Turning a bot update into what an agent — or a single model call — is told. Pure; never throws. */
|
|
3
3
|
export interface BotAgentHelper {
|
|
4
4
|
/**
|
|
5
|
-
* The
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* The instruction text: one instruction, or several (the instructions of the application's
|
|
6
|
+
* keyword groups the message matched, in the order the handler passes them) — each trimmed,
|
|
7
|
+
* empties and repeats dropped, joined by a blank line. `''` when there is none. For a caller that
|
|
8
|
+
* asks ONE model call (no tools) and needs the owner's guidance as a single text.
|
|
8
9
|
*/
|
|
9
|
-
instructionOf: (
|
|
10
|
+
instructionOf: (instruction?: string | readonly string[]) => string;
|
|
10
11
|
/**
|
|
11
12
|
* The context `botAgentPlugin` contributes, one markdown block each: the message (platform, chat,
|
|
12
|
-
* sender, text quoted as data, the replied-to text),
|
|
13
|
-
*
|
|
13
|
+
* sender, text quoted as data, the replied-to text) and, when `instruction` holds any text, the
|
|
14
|
+
* instruction block (`# Instruction`, `instructionOf(instruction)`).
|
|
14
15
|
*/
|
|
15
|
-
contextOf: (update: BotUpdate, instruction?: string) => string[];
|
|
16
|
+
contextOf: (update: BotUpdate, instruction?: string | readonly string[]) => string[];
|
|
16
17
|
/** The opening turn to invoke the agent with: act on the message as the context instructs, through the tools. */
|
|
17
18
|
promptOf: (update: BotUpdate) => string;
|
|
18
19
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/helpers/agent/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAE9C,sGAAsG;AACtG,MAAM,WAAW,cAAc;IAC7B
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/helpers/agent/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAE9C,sGAAsG;AACtG,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,aAAa,EAAE,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,KAAK,MAAM,CAAA;IACnE;;;;OAIG;IACH,SAAS,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,KAAK,MAAM,EAAE,CAAA;IACpF,iHAAiH;IACjH,QAAQ,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,MAAM,CAAA;CACxC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/helpers/agent.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAEtD,eAAO,MAAM,oBAAoB,QAAO,
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/helpers/agent.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAEtD,eAAO,MAAM,oBAAoB,QAAO,cA8FvC,CAAA;AAED,eAAO,MAAM,cAAc,gBAAyB,CAAA"}
|
package/build/helpers/agent.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BOT_PLATFORMS,
|
|
1
|
+
import { BOT_PLATFORMS, BotUpdateKind } from '@owlmeans/bot';
|
|
2
2
|
import { BOT_AGENT_DONE } from '../consts.js';
|
|
3
3
|
export const createBotAgentHelper = () => {
|
|
4
4
|
/** A person's words as a quote block — read as data, never as headings or instructions. */
|
|
@@ -17,10 +17,12 @@ export const createBotAgentHelper = () => {
|
|
|
17
17
|
const title = update.chat.title?.trim() ?? '';
|
|
18
18
|
return title !== '' ? `${update.chat.kind} "${title}"` : update.chat.kind;
|
|
19
19
|
};
|
|
20
|
-
const instructionOf =
|
|
20
|
+
const instructionOf = instruction => {
|
|
21
|
+
const list = typeof instruction === 'string' ? [instruction]
|
|
22
|
+
: Array.isArray(instruction) ? instruction : [];
|
|
21
23
|
const seen = new Set();
|
|
22
24
|
const parts = [];
|
|
23
|
-
for (const text of
|
|
25
|
+
for (const text of list) {
|
|
24
26
|
const trimmed = typeof text === 'string' ? text.trim() : '';
|
|
25
27
|
if (trimmed !== '' && !seen.has(trimmed)) {
|
|
26
28
|
seen.add(trimmed);
|
|
@@ -54,38 +56,11 @@ export const createBotAgentHelper = () => {
|
|
|
54
56
|
}
|
|
55
57
|
return lines.join('\n');
|
|
56
58
|
};
|
|
57
|
-
const groupsBlock = (update) => {
|
|
58
|
-
const matches = update.matches ?? [];
|
|
59
|
-
if (matches.length === 0) {
|
|
60
|
-
return '# Matched keyword groups\n\nNone — the message matched no keyword group of the bot\'s owner.';
|
|
61
|
-
}
|
|
62
|
-
const groups = matches.map(({ rule, keywords }, index) => {
|
|
63
|
-
const lines = [`## ${index + 1}. ${rule.name}`, `Keywords matched: ${keywords.join(', ')}`];
|
|
64
|
-
if (rule.action !== BotRuleAction.Agent) {
|
|
65
|
-
lines.push(`Action: ${rule.action}`);
|
|
66
|
-
}
|
|
67
|
-
if (typeof rule.reply === 'string' && rule.reply.trim() !== '') {
|
|
68
|
-
lines.push(`Reply text: ${rule.reply.trim()}`);
|
|
69
|
-
}
|
|
70
|
-
if (typeof rule.instruction === 'string' && rule.instruction.trim() !== '') {
|
|
71
|
-
lines.push(`Instruction: ${rule.instruction.trim()}`);
|
|
72
|
-
}
|
|
73
|
-
return lines.join('\n');
|
|
74
|
-
});
|
|
75
|
-
return [
|
|
76
|
-
'# Matched keyword groups',
|
|
77
|
-
'',
|
|
78
|
-
'The message matched these keyword groups of the bot\'s owner, highest priority first. '
|
|
79
|
-
+ 'Follow their instructions.',
|
|
80
|
-
'',
|
|
81
|
-
groups.join('\n\n'),
|
|
82
|
-
].join('\n');
|
|
83
|
-
};
|
|
84
59
|
const contextOf = (update, instruction) => {
|
|
85
|
-
const blocks = [messageBlock(update)
|
|
86
|
-
const
|
|
87
|
-
if (
|
|
88
|
-
blocks.push(`# Instruction\n\n${
|
|
60
|
+
const blocks = [messageBlock(update)];
|
|
61
|
+
const text = instructionOf(instruction);
|
|
62
|
+
if (text !== '') {
|
|
63
|
+
blocks.push(`# Instruction\n\n${text}`);
|
|
89
64
|
}
|
|
90
65
|
return blocks;
|
|
91
66
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/helpers/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/helpers/agent.ts"],"names":[],"mappings":"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"}
|
package/build/plugin.d.ts
CHANGED
|
@@ -4,8 +4,9 @@ import type { BotAgentOptions } from './types.js';
|
|
|
4
4
|
* Lets an `@owlmeans/agent` agent act on ONE chat-bot update.
|
|
5
5
|
*
|
|
6
6
|
* `context` contributes the message — platform, chat, sender, the text quoted as data, the replied-to
|
|
7
|
-
* text —
|
|
8
|
-
* in the agent's project message (`PromptBlock.Context`),
|
|
7
|
+
* text — and, when given, the instruction block: the instructions of the application's keyword groups
|
|
8
|
+
* the handler matched (`instruction`). It lands in the agent's project message (`PromptBlock.Context`),
|
|
9
|
+
* never in the cached system prompt.
|
|
9
10
|
* `tools` are `botAgentTools(options)`: `reply`, `delete`, `warn`, `mute`, `ban`, `react` — only
|
|
10
11
|
* those the platform supports and `allow` names — each acting on the update and never throwing.
|
|
11
12
|
*
|
package/build/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAIlD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAEjD
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAIlD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAEjD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,cAAc,YAAa,eAAe,KAAG,WAKxD,CAAA"}
|
package/build/plugin.js
CHANGED
|
@@ -5,8 +5,9 @@ import { botAgentTools } from './tools.js';
|
|
|
5
5
|
* Lets an `@owlmeans/agent` agent act on ONE chat-bot update.
|
|
6
6
|
*
|
|
7
7
|
* `context` contributes the message — platform, chat, sender, the text quoted as data, the replied-to
|
|
8
|
-
* text —
|
|
9
|
-
* in the agent's project message (`PromptBlock.Context`),
|
|
8
|
+
* text — and, when given, the instruction block: the instructions of the application's keyword groups
|
|
9
|
+
* the handler matched (`instruction`). It lands in the agent's project message (`PromptBlock.Context`),
|
|
10
|
+
* never in the cached system prompt.
|
|
10
11
|
* `tools` are `botAgentTools(options)`: `reply`, `delete`, `warn`, `mute`, `ban`, `react` — only
|
|
11
12
|
* those the platform supports and `allow` names — each acting on the update and never throwing.
|
|
12
13
|
*
|
package/build/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAC/D,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAG1C
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAC/D,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAG1C;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,OAAwB,EAAe,EAAE,CAAC,CAAC;IACxE,KAAK,EAAE,gBAAgB;IACvB,KAAK,EAAE,eAAe;IACtB,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,cAAc,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,WAAW,CAAC;IAClF,KAAK,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,OAAO,CAAC;CACpC,CAAC,CAAA"}
|
package/build/tools.d.ts
CHANGED
|
@@ -8,7 +8,8 @@ import type { BotAgentToolsOptions } from './types.js';
|
|
|
8
8
|
* update has what it acts on (a message for `reply` / `warn` / `delete` / `react`, a sender for
|
|
9
9
|
* `mute` / `ban`). Every tool NEVER throws: it answers `done`, or `{ error }` with the action's
|
|
10
10
|
* one-sentence `error` — the `toolHelper` contract, so a refused action is something the model reads
|
|
11
|
-
* and adapts to.
|
|
11
|
+
* and adapts to. Every action records `ref` (when given) on its activity — the application's own
|
|
12
|
+
* record the handler followed.
|
|
12
13
|
*/
|
|
13
14
|
export declare const botAgentTools: (options: BotAgentToolsOptions) => AgentToolSet;
|
|
14
15
|
//# sourceMappingURL=tools.d.ts.map
|
package/build/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAEA,OAAO,EAAc,KAAK,YAAY,EAA0B,MAAM,iBAAiB,CAAA;AAKvF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAOtD
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAEA,OAAO,EAAc,KAAK,YAAY,EAA0B,MAAM,iBAAiB,CAAA;AAKvF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAOtD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,aAAa,YAAa,oBAAoB,KAAG,YAoE7D,CAAA"}
|
package/build/tools.js
CHANGED
|
@@ -12,13 +12,13 @@ import { BotAgentMuteSchema, BotAgentNoArgsSchema, BotAgentReactSchema, BotAgent
|
|
|
12
12
|
* update has what it acts on (a message for `reply` / `warn` / `delete` / `react`, a sender for
|
|
13
13
|
* `mute` / `ban`). Every tool NEVER throws: it answers `done`, or `{ error }` with the action's
|
|
14
14
|
* one-sentence `error` — the `toolHelper` contract, so a refused action is something the model reads
|
|
15
|
-
* and adapts to.
|
|
15
|
+
* and adapts to. Every action records `ref` (when given) on its activity — the application's own
|
|
16
|
+
* record the handler followed.
|
|
16
17
|
*/
|
|
17
18
|
export const botAgentTools = (options) => {
|
|
18
19
|
const { update, bot } = options;
|
|
19
20
|
const allowed = new Set(options.allow ?? BOT_AGENT_ACTIONS);
|
|
20
|
-
const
|
|
21
|
-
const opts = rule != null ? { rule } : {};
|
|
21
|
+
const opts = options.ref != null && options.ref !== '' ? { ref: options.ref } : {};
|
|
22
22
|
const supported = (kind) => {
|
|
23
23
|
try {
|
|
24
24
|
return bot.supports(kind);
|
package/build/tools.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAgC,MAAM,uBAAuB,CAAA;AAE1E,OAAO,EAAE,UAAU,EAA6C,MAAM,iBAAiB,CAAA;AAGvF,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AACrG,OAAO,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAA;AAEnF,OAAO,EAAE,mBAAmB,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAA;AAC1F,OAAO,EACL,kBAAkB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,kBAAkB,GACtG,MAAM,oBAAoB,CAAA;AAG3B
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
|
package/build/types.d.ts
CHANGED
|
@@ -13,9 +13,18 @@ export interface BotAgentToolsOptions {
|
|
|
13
13
|
* with `bot.supports(kind)`: a tool the platform cannot perform is never offered.
|
|
14
14
|
*/
|
|
15
15
|
allow?: readonly BotActionKind[];
|
|
16
|
+
/**
|
|
17
|
+
* An opaque reference every action records on its activity (`{ ref }`) — the id of the
|
|
18
|
+
* application's own record the handler followed (the keyword group the message matched).
|
|
19
|
+
*/
|
|
20
|
+
ref?: string;
|
|
16
21
|
}
|
|
17
22
|
export interface BotAgentOptions extends BotAgentToolsOptions {
|
|
18
|
-
/**
|
|
19
|
-
|
|
23
|
+
/**
|
|
24
|
+
* What the model is told for this message: the instructions of the application's keyword groups
|
|
25
|
+
* the message matched (the handler matches them and passes them here), or one instruction of the
|
|
26
|
+
* product's own. Absent or blank: the context is the message alone.
|
|
27
|
+
*/
|
|
28
|
+
instruction?: string | readonly string[];
|
|
20
29
|
}
|
|
21
30
|
//# sourceMappingURL=types.d.ts.map
|
package/build/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAC7D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAEtD,MAAM,WAAW,oBAAoB;IACnC,sEAAsE;IACtE,MAAM,EAAE,SAAS,CAAA;IACjB;;;OAGG;IACH,GAAG,EAAE,UAAU,CAAA;IACf;;;OAGG;IACH,KAAK,CAAC,EAAE,SAAS,aAAa,EAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAC7D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAEtD,MAAM,WAAW,oBAAoB;IACnC,sEAAsE;IACtE,MAAM,EAAE,SAAS,CAAA;IACjB;;;OAGG;IACH,GAAG,EAAE,UAAU,CAAA;IACf;;;OAGG;IACH,KAAK,CAAC,EAAE,SAAS,aAAa,EAAE,CAAA;IAChC;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,eAAgB,SAAQ,oBAAoB;IAC3D;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAA;CACzC"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owlmeans/bot-agent",
|
|
3
|
-
"version": "0.1.18
|
|
3
|
+
"version": "0.1.18",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"description": "The bridge between an OwlMeans chat bot and an @owlmeans/agent agent
|
|
5
|
+
"description": "The bridge between an OwlMeans chat bot and an @owlmeans/agent agent \u2014 botAgentPlugin contributes a bot update (platform, chat, sender, message) and the instructions its handler passes as the agent's context and the bot's actions (reply, delete, warn, mute, ban, react) as never-throwing tools, limited to what the platform supports and the caller allows.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "tsc -b",
|
|
@@ -23,24 +23,24 @@
|
|
|
23
23
|
}
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@owlmeans/bot": "^0.1.18
|
|
27
|
-
"@owlmeans/server-bot": "^0.1.18
|
|
26
|
+
"@owlmeans/bot": "^0.1.18",
|
|
27
|
+
"@owlmeans/server-bot": "^0.1.18"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"@langchain/core": "^1.2.14",
|
|
31
|
-
"@owlmeans/agent": "^0.1.18
|
|
31
|
+
"@owlmeans/agent": "^0.1.18"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@langchain/core": "^1.2.14",
|
|
35
35
|
"@langchain/langgraph": "^1.4.19",
|
|
36
36
|
"@langchain/langgraph-checkpoint": "^1.1.5",
|
|
37
|
-
"@owlmeans/agent": "^0.1.18
|
|
38
|
-
"@owlmeans/context": "^0.1.18
|
|
39
|
-
"@owlmeans/dep-config": "
|
|
40
|
-
"@owlmeans/llm": "^0.1.18
|
|
41
|
-
"@owlmeans/llm-common": "^0.1.18
|
|
42
|
-
"@owlmeans/log": "^0.1.18
|
|
43
|
-
"@owlmeans/static-resource": "^0.1.18
|
|
37
|
+
"@owlmeans/agent": "^0.1.18",
|
|
38
|
+
"@owlmeans/context": "^0.1.18",
|
|
39
|
+
"@owlmeans/dep-config": "^0.1.18",
|
|
40
|
+
"@owlmeans/llm": "^0.1.18",
|
|
41
|
+
"@owlmeans/llm-common": "^0.1.18",
|
|
42
|
+
"@owlmeans/log": "^0.1.18",
|
|
43
|
+
"@owlmeans/static-resource": "^0.1.18",
|
|
44
44
|
"@types/bun": "^1.4.2",
|
|
45
45
|
"@types/node": "^26.6.4",
|
|
46
46
|
"ajv": "^8.20.0",
|
|
@@ -3,17 +3,18 @@ import type { BotUpdate } from '@owlmeans/bot'
|
|
|
3
3
|
/** Turning a bot update into what an agent — or a single model call — is told. Pure; never throws. */
|
|
4
4
|
export interface BotAgentHelper {
|
|
5
5
|
/**
|
|
6
|
-
* The
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* The instruction text: one instruction, or several (the instructions of the application's
|
|
7
|
+
* keyword groups the message matched, in the order the handler passes them) — each trimmed,
|
|
8
|
+
* empties and repeats dropped, joined by a blank line. `''` when there is none. For a caller that
|
|
9
|
+
* asks ONE model call (no tools) and needs the owner's guidance as a single text.
|
|
9
10
|
*/
|
|
10
|
-
instructionOf: (
|
|
11
|
+
instructionOf: (instruction?: string | readonly string[]) => string
|
|
11
12
|
/**
|
|
12
13
|
* The context `botAgentPlugin` contributes, one markdown block each: the message (platform, chat,
|
|
13
|
-
* sender, text quoted as data, the replied-to text),
|
|
14
|
-
*
|
|
14
|
+
* sender, text quoted as data, the replied-to text) and, when `instruction` holds any text, the
|
|
15
|
+
* instruction block (`# Instruction`, `instructionOf(instruction)`).
|
|
15
16
|
*/
|
|
16
|
-
contextOf: (update: BotUpdate, instruction?: string) => string[]
|
|
17
|
+
contextOf: (update: BotUpdate, instruction?: string | readonly string[]) => string[]
|
|
17
18
|
/** The opening turn to invoke the agent with: act on the message as the context instructs, through the tools. */
|
|
18
19
|
promptOf: (update: BotUpdate) => string
|
|
19
20
|
}
|
package/src/helpers/agent.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BOT_PLATFORMS,
|
|
1
|
+
import { BOT_PLATFORMS, BotUpdateKind, type BotSenderRef, type BotUpdate } from '@owlmeans/bot'
|
|
2
2
|
import { BOT_AGENT_DONE } from '../consts.js'
|
|
3
3
|
import type { BotAgentHelper } from './agent/types.js'
|
|
4
4
|
|
|
@@ -24,10 +24,12 @@ export const createBotAgentHelper = (): BotAgentHelper => {
|
|
|
24
24
|
return title !== '' ? `${update.chat.kind} "${title}"` : update.chat.kind
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
const instructionOf: BotAgentHelper['instructionOf'] =
|
|
27
|
+
const instructionOf: BotAgentHelper['instructionOf'] = instruction => {
|
|
28
|
+
const list: readonly unknown[] = typeof instruction === 'string' ? [instruction]
|
|
29
|
+
: Array.isArray(instruction) ? instruction : []
|
|
28
30
|
const seen = new Set<string>()
|
|
29
31
|
const parts: string[] = []
|
|
30
|
-
for (const text of
|
|
32
|
+
for (const text of list) {
|
|
31
33
|
const trimmed = typeof text === 'string' ? text.trim() : ''
|
|
32
34
|
if (trimmed !== '' && !seen.has(trimmed)) {
|
|
33
35
|
seen.add(trimmed)
|
|
@@ -75,42 +77,11 @@ export const createBotAgentHelper = (): BotAgentHelper => {
|
|
|
75
77
|
return lines.join('\n')
|
|
76
78
|
}
|
|
77
79
|
|
|
78
|
-
const groupsBlock = (update: BotUpdate): string => {
|
|
79
|
-
const matches = update.matches ?? []
|
|
80
|
-
if (matches.length === 0) {
|
|
81
|
-
return '# Matched keyword groups\n\nNone — the message matched no keyword group of the bot\'s owner.'
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const groups = matches.map(({ rule, keywords }, index) => {
|
|
85
|
-
const lines = [`## ${index + 1}. ${rule.name}`, `Keywords matched: ${keywords.join(', ')}`]
|
|
86
|
-
if (rule.action !== BotRuleAction.Agent) {
|
|
87
|
-
lines.push(`Action: ${rule.action}`)
|
|
88
|
-
}
|
|
89
|
-
if (typeof rule.reply === 'string' && rule.reply.trim() !== '') {
|
|
90
|
-
lines.push(`Reply text: ${rule.reply.trim()}`)
|
|
91
|
-
}
|
|
92
|
-
if (typeof rule.instruction === 'string' && rule.instruction.trim() !== '') {
|
|
93
|
-
lines.push(`Instruction: ${rule.instruction.trim()}`)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return lines.join('\n')
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
return [
|
|
100
|
-
'# Matched keyword groups',
|
|
101
|
-
'',
|
|
102
|
-
'The message matched these keyword groups of the bot\'s owner, highest priority first. '
|
|
103
|
-
+ 'Follow their instructions.',
|
|
104
|
-
'',
|
|
105
|
-
groups.join('\n\n'),
|
|
106
|
-
].join('\n')
|
|
107
|
-
}
|
|
108
|
-
|
|
109
80
|
const contextOf: BotAgentHelper['contextOf'] = (update, instruction) => {
|
|
110
|
-
const blocks = [messageBlock(update)
|
|
111
|
-
const
|
|
112
|
-
if (
|
|
113
|
-
blocks.push(`# Instruction\n\n${
|
|
81
|
+
const blocks = [messageBlock(update)]
|
|
82
|
+
const text = instructionOf(instruction)
|
|
83
|
+
if (text !== '') {
|
|
84
|
+
blocks.push(`# Instruction\n\n${text}`)
|
|
114
85
|
}
|
|
115
86
|
|
|
116
87
|
return blocks
|
package/src/plugin.ts
CHANGED
|
@@ -8,8 +8,9 @@ import type { BotAgentOptions } from './types.js'
|
|
|
8
8
|
* Lets an `@owlmeans/agent` agent act on ONE chat-bot update.
|
|
9
9
|
*
|
|
10
10
|
* `context` contributes the message — platform, chat, sender, the text quoted as data, the replied-to
|
|
11
|
-
* text —
|
|
12
|
-
* in the agent's project message (`PromptBlock.Context`),
|
|
11
|
+
* text — and, when given, the instruction block: the instructions of the application's keyword groups
|
|
12
|
+
* the handler matched (`instruction`). It lands in the agent's project message (`PromptBlock.Context`),
|
|
13
|
+
* never in the cached system prompt.
|
|
13
14
|
* `tools` are `botAgentTools(options)`: `reply`, `delete`, `warn`, `mute`, `ban`, `react` — only
|
|
14
15
|
* those the platform supports and `allow` names — each acting on the update and never throwing.
|
|
15
16
|
*
|
package/src/tools.ts
CHANGED
|
@@ -20,13 +20,13 @@ import type { BotAgentToolArgs } from './tools/types.local.js'
|
|
|
20
20
|
* update has what it acts on (a message for `reply` / `warn` / `delete` / `react`, a sender for
|
|
21
21
|
* `mute` / `ban`). Every tool NEVER throws: it answers `done`, or `{ error }` with the action's
|
|
22
22
|
* one-sentence `error` — the `toolHelper` contract, so a refused action is something the model reads
|
|
23
|
-
* and adapts to.
|
|
23
|
+
* and adapts to. Every action records `ref` (when given) on its activity — the application's own
|
|
24
|
+
* record the handler followed.
|
|
24
25
|
*/
|
|
25
26
|
export const botAgentTools = (options: BotAgentToolsOptions): AgentToolSet => {
|
|
26
27
|
const { update, bot } = options
|
|
27
28
|
const allowed = new Set<BotActionKind>(options.allow ?? BOT_AGENT_ACTIONS)
|
|
28
|
-
const
|
|
29
|
-
const opts: BotActionOptions = rule != null ? { rule } : {}
|
|
29
|
+
const opts: BotActionOptions = options.ref != null && options.ref !== '' ? { ref: options.ref } : {}
|
|
30
30
|
|
|
31
31
|
const supported = (kind: BotActionKind): boolean => {
|
|
32
32
|
try {
|
package/src/types.ts
CHANGED
|
@@ -14,9 +14,18 @@ export interface BotAgentToolsOptions {
|
|
|
14
14
|
* with `bot.supports(kind)`: a tool the platform cannot perform is never offered.
|
|
15
15
|
*/
|
|
16
16
|
allow?: readonly BotActionKind[]
|
|
17
|
+
/**
|
|
18
|
+
* An opaque reference every action records on its activity (`{ ref }`) — the id of the
|
|
19
|
+
* application's own record the handler followed (the keyword group the message matched).
|
|
20
|
+
*/
|
|
21
|
+
ref?: string
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
export interface BotAgentOptions extends BotAgentToolsOptions {
|
|
20
|
-
/**
|
|
21
|
-
|
|
25
|
+
/**
|
|
26
|
+
* What the model is told for this message: the instructions of the application's keyword groups
|
|
27
|
+
* the message matched (the handler matches them and passes them here), or one instruction of the
|
|
28
|
+
* product's own. Absent or blank: the context is the message alone.
|
|
29
|
+
*/
|
|
30
|
+
instruction?: string | readonly string[]
|
|
22
31
|
}
|
package/tests/context.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto'
|
|
2
2
|
import { AppType, makeBasicContext, type BasicConfig, type BasicContext } from '@owlmeans/context'
|
|
3
|
-
import { BotPlatform, BotTransport,
|
|
3
|
+
import { BotPlatform, BotTransport, botTextHelper, type BotSimulateInput, type BotUpdate } from '@owlmeans/bot'
|
|
4
4
|
import { configureLog } from '@owlmeans/log'
|
|
5
5
|
import {
|
|
6
|
-
appendBot, botHandler, RES_BOT_ACTIVITY, RES_BOT_CONNECTION,
|
|
7
|
-
type BotActivityRecord, type BotConnectionRecord, type BotHandle, type
|
|
8
|
-
type BotUpdateRecord,
|
|
6
|
+
appendBot, botHandler, RES_BOT_ACTIVITY, RES_BOT_CONNECTION, RES_BOT_UPDATE, type BotActions,
|
|
7
|
+
type BotActivityRecord, type BotConnectionRecord, type BotHandle, type BotService, type BotUpdateRecord,
|
|
9
8
|
} from '@owlmeans/server-bot'
|
|
10
9
|
import { createStaticResource } from '@owlmeans/static-resource'
|
|
11
10
|
|
|
@@ -21,14 +20,13 @@ export interface TestBot {
|
|
|
21
20
|
let stores = 0
|
|
22
21
|
|
|
23
22
|
/**
|
|
24
|
-
* One real context per test: the
|
|
23
|
+
* One real context per test: the three bot resources as `@owlmeans/static-resource` stores (a fresh
|
|
25
24
|
* key each time), then `appendBot` — what a target's backend does with its Postgres resources.
|
|
26
25
|
*/
|
|
27
26
|
export const makeTestBot = (): TestBot => {
|
|
28
27
|
const key = `bot-agent-tests-${++stores}`
|
|
29
28
|
const context = makeBasicContext<BasicConfig>({ ready: false, service: 'bot-agent-tests', type: AppType.Backend, services: {} })
|
|
30
29
|
context.registerResource(createStaticResource<BotConnectionRecord>(RES_BOT_CONNECTION, `${key}-connection`))
|
|
31
|
-
context.registerResource(createStaticResource<BotRuleRecord>(RES_BOT_RULE, `${key}-rule`))
|
|
32
30
|
context.registerResource(createStaticResource<BotUpdateRecord>(RES_BOT_UPDATE, `${key}-update`))
|
|
33
31
|
context.registerResource(createStaticResource<BotActivityRecord>(RES_BOT_ACTIVITY, `${key}-activity`))
|
|
34
32
|
const service = appendBot(context, { secret: () => 'bot-agent-tests-secret' })
|
|
@@ -49,19 +47,14 @@ export interface Received {
|
|
|
49
47
|
}
|
|
50
48
|
|
|
51
49
|
/**
|
|
52
|
-
* A message through the platform's own path (Telegram unless `platform` says otherwise):
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* target's would.
|
|
50
|
+
* A message through the platform's own path (Telegram unless `platform` says otherwise): simulated on
|
|
51
|
+
* a mock connection and dispatched by the worker's runtime to `handle` — the update and the actions a
|
|
52
|
+
* handler receives. `handle` runs inside the handler, as a target's would.
|
|
56
53
|
*/
|
|
57
54
|
export const receive = async (
|
|
58
|
-
bot: TestBot, input: BotSimulateInput,
|
|
59
|
-
platform: BotPlatform = BotPlatform.Telegram,
|
|
55
|
+
bot: TestBot, input: BotSimulateInput, handle?: BotHandle, platform: BotPlatform = BotPlatform.Telegram,
|
|
60
56
|
): Promise<Received> => {
|
|
61
57
|
const connection = await connect(bot, platform)
|
|
62
|
-
for (const rule of rules) {
|
|
63
|
-
await bot.service.rules.save(bot.scope, rule)
|
|
64
|
-
}
|
|
65
58
|
let received: Received | null = null
|
|
66
59
|
const runtime = bot.service.runtime([botHandler('agent', async (update, actions, context) => {
|
|
67
60
|
received = { connection, update, actions }
|
|
@@ -82,4 +75,15 @@ export const receive = async (
|
|
|
82
75
|
* a platform that can reply, send and react, never delete, mute or ban.
|
|
83
76
|
*/
|
|
84
77
|
export const whatsAppUpdate = async (bot: TestBot): Promise<Received> =>
|
|
85
|
-
await receive(bot, { text: 'When do you open?', sender: { id: '4915550001', name: 'Ann' } },
|
|
78
|
+
await receive(bot, { text: 'When do you open?', sender: { id: '4915550001', name: 'Ann' } }, undefined, BotPlatform.WhatsApp)
|
|
79
|
+
|
|
80
|
+
/** An application's own keyword group, as a generated handler reads it from the app's records. */
|
|
81
|
+
export interface KeywordGroup {
|
|
82
|
+
id: string
|
|
83
|
+
keywords: string[]
|
|
84
|
+
instruction?: string
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** What a generated handler does with its groups: the ones the text matches, in the given order. */
|
|
88
|
+
export const matchedGroups = (text: string, groups: readonly KeywordGroup[]): KeywordGroup[] =>
|
|
89
|
+
groups.filter(group => botTextHelper.matched(text, group.keywords).length > 0)
|
package/tests/helper.spec.ts
CHANGED
|
@@ -1,31 +1,29 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test'
|
|
2
|
-
import { BotChatKind
|
|
2
|
+
import { BotChatKind } from '@owlmeans/bot'
|
|
3
3
|
import { botAgentHelper } from '../src/index.js'
|
|
4
4
|
import { makeTestBot, receive } from './context.js'
|
|
5
5
|
|
|
6
6
|
describe('@owlmeans/bot-agent — botAgentHelper', () => {
|
|
7
|
-
test('instructionOf joins the
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
expect(botAgentHelper.instructionOf(update, 'Answer in English.')).toBe('Delete advertising.\n\nAnswer in English.')
|
|
16
|
-
expect(botAgentHelper.instructionOf({ ...update, matches: [] })).toBe('')
|
|
7
|
+
test('instructionOf joins the given instructions — trimmed, empties and repeats dropped', () => {
|
|
8
|
+
expect(botAgentHelper.instructionOf([' Delete advertising. ', 'Delete advertising.', ' ', 'Answer in English.']))
|
|
9
|
+
.toBe('Delete advertising.\n\nAnswer in English.')
|
|
10
|
+
expect(botAgentHelper.instructionOf(' Be brief. ')).toBe('Be brief.')
|
|
11
|
+
expect(botAgentHelper.instructionOf(' ')).toBe('')
|
|
12
|
+
expect(botAgentHelper.instructionOf([])).toBe('')
|
|
13
|
+
expect(botAgentHelper.instructionOf()).toBe('')
|
|
17
14
|
})
|
|
18
15
|
|
|
19
|
-
test('contextOf says what is absent rather than inventing it', async () => {
|
|
16
|
+
test('contextOf says what is absent rather than inventing it, and adds no empty instruction block', async () => {
|
|
20
17
|
const bot = makeTestBot()
|
|
21
18
|
const { update } = await receive(bot, { text: 'hi there', chat: { kind: BotChatKind.Private } })
|
|
22
19
|
|
|
23
20
|
const blocks = botAgentHelper.contextOf({ ...update, sender: undefined, message: undefined }, ' ')
|
|
24
|
-
expect(blocks).toHaveLength(
|
|
21
|
+
expect(blocks).toHaveLength(1)
|
|
25
22
|
expect(blocks[0]).toContain('Chat: private')
|
|
26
23
|
expect(blocks[0]).not.toContain('Sender:')
|
|
27
24
|
expect(blocks[0]).toContain('The update carries no message.')
|
|
28
|
-
expect(blocks[
|
|
25
|
+
expect(blocks[0]).not.toContain('keyword')
|
|
26
|
+
expect(botAgentHelper.contextOf(update, ['Greet back.', 'Be brief.'])[1]).toBe('# Instruction\n\nGreet back.\n\nBe brief.')
|
|
29
27
|
})
|
|
30
28
|
|
|
31
29
|
test('promptOf names the platform and the chat, and the tools\' answers', async () => {
|
package/tests/loop.spec.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test'
|
|
2
2
|
import type { ToolMessage } from '@langchain/core/messages'
|
|
3
3
|
import { makeAgentModel, type AgentModel, type AgentResult } from '@owlmeans/agent'
|
|
4
|
-
import { BotActionKind
|
|
4
|
+
import { BotActionKind } from '@owlmeans/bot'
|
|
5
5
|
import { DEFAULT_EFFORT, makeExecutionService, makePromptService } from '@owlmeans/llm'
|
|
6
6
|
import { BOT_AGENT_DONE, BotAgentTool, botAgentHelper, botAgentPlugin } from '../src/index.js'
|
|
7
|
-
import { makeTestBot, receive } from './context.js'
|
|
7
|
+
import { makeTestBot, matchedGroups, receive, type KeywordGroup } from './context.js'
|
|
8
8
|
import { scriptedModel, type ScriptedModel, type ScriptedTurn } from './_tools/model.js'
|
|
9
9
|
|
|
10
10
|
const ROLE = 'You moderate a community chat for its owner. Judge each message against the matched '
|
|
@@ -24,8 +24,9 @@ const exec = executions.root({
|
|
|
24
24
|
const makeModeratorAgent = (scripted: ScriptedModel): AgentModel =>
|
|
25
25
|
makeAgentModel({ exec, agentModel: scripted.model, tools: {}, maxTurns: 6 })
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
/** The application's own keyword groups, as its generated handler reads them. */
|
|
28
|
+
const GROUPS: KeywordGroup[] = [
|
|
29
|
+
{ id: 'group-spam', keywords: ['casino'], instruction: 'Delete advertising and warn the sender.' },
|
|
29
30
|
]
|
|
30
31
|
|
|
31
32
|
/** A message simulated on a mock Telegram connection; the handler runs the agent over it. */
|
|
@@ -33,10 +34,16 @@ const moderate = async (text: string, script: ScriptedTurn[]) => {
|
|
|
33
34
|
const bot = makeTestBot()
|
|
34
35
|
const scripted = scriptedModel(script)
|
|
35
36
|
let result: AgentResult | null = null
|
|
36
|
-
const received = await receive(bot, { text, sender: { id: '777', name: 'Spammer' } },
|
|
37
|
+
const received = await receive(bot, { text, sender: { id: '777', name: 'Spammer' } }, async (update, actions) => {
|
|
38
|
+
// The generated handler: match the app's groups, hand their instructions and the first one's id over.
|
|
39
|
+
const matched = matchedGroups(update.message?.text ?? '', GROUPS)
|
|
40
|
+
if (matched.length === 0) {
|
|
41
|
+
return
|
|
42
|
+
}
|
|
37
43
|
const agent = makeModeratorAgent(scripted)
|
|
38
44
|
agent.use(botAgentPlugin({
|
|
39
45
|
update, bot: actions, allow: [BotActionKind.Delete, BotActionKind.Reply, BotActionKind.Mute],
|
|
46
|
+
ref: matched[0].id, instruction: matched.map(group => group.instruction ?? ''),
|
|
40
47
|
}))
|
|
41
48
|
result = await agent.invoke(botAgentHelper.promptOf(update), { action: 'moderate' })
|
|
42
49
|
})
|
|
@@ -72,16 +79,15 @@ describe('@owlmeans/bot-agent — an agent loop over a bot update', () => {
|
|
|
72
79
|
expect(JSON.stringify(system.content)).not.toContain('Best casino in town')
|
|
73
80
|
expect(project.getType()).toBe('human')
|
|
74
81
|
expect(JSON.stringify(project.content)).toContain('> Best casino in town')
|
|
75
|
-
expect(JSON.stringify(project.content)).toContain('Instruction
|
|
82
|
+
expect(JSON.stringify(project.content)).toContain('# Instruction\\n\\nDelete advertising and warn the sender.')
|
|
76
83
|
|
|
77
84
|
expect(tools.map(message => [message.name, message.content, message.status]))
|
|
78
85
|
.toEqual([[BotAgentTool.Delete, BOT_AGENT_DONE, 'success'], [BotAgentTool.Warn, BOT_AGENT_DONE, 'success']])
|
|
79
86
|
expect(result.message.content).toBe('Deleted the casino ad and warned the sender.')
|
|
80
|
-
const rule = received.update.matches[0].rule.id
|
|
81
87
|
const activity = (await bot.service.activity.list(bot.scope)).reverse()
|
|
82
|
-
expect(activity.map(entry => [entry.action, entry.ok, entry.update, entry.
|
|
83
|
-
[BotActionKind.Delete, true, received.update.id,
|
|
84
|
-
[BotActionKind.Reply, true, received.update.id,
|
|
88
|
+
expect(activity.map(entry => [entry.action, entry.ok, entry.update, entry.ref, entry.text ?? null])).toEqual([
|
|
89
|
+
[BotActionKind.Delete, true, received.update.id, 'group-spam', null],
|
|
90
|
+
[BotActionKind.Reply, true, received.update.id, 'group-spam', 'Advertising is not allowed here.'],
|
|
85
91
|
])
|
|
86
92
|
})
|
|
87
93
|
|
package/tests/plugin.spec.ts
CHANGED
|
@@ -1,32 +1,37 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test'
|
|
2
2
|
import { makeAgentModel, type AgentRun } from '@owlmeans/agent'
|
|
3
|
-
import { BotActionKind
|
|
3
|
+
import { BotActionKind } from '@owlmeans/bot'
|
|
4
4
|
import { DEFAULT_EFFORT, makeExecutionService, makePromptService } from '@owlmeans/llm'
|
|
5
5
|
import { BOT_AGENT_PLUGIN, BotAgentTool, botAgentPlugin } from '../src/index.js'
|
|
6
|
-
import { makeTestBot, receive, whatsAppUpdate } from './context.js'
|
|
6
|
+
import { makeTestBot, matchedGroups, receive, whatsAppUpdate, type KeywordGroup } from './context.js'
|
|
7
7
|
import { scriptedModel } from './_tools/model.js'
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
{
|
|
12
|
-
{
|
|
9
|
+
/** The application's own keyword groups — the handler matches them, the plugin only hears their instructions. */
|
|
10
|
+
const GROUPS: KeywordGroup[] = [
|
|
11
|
+
{ id: 'g-spam', keywords: ['casino', 'free money'], instruction: 'Delete advertising.' },
|
|
12
|
+
{ id: 'g-rude', keywords: ['idiot'], instruction: 'Warn politely.' },
|
|
13
|
+
{ id: 'g-weather', keywords: ['rain'], instruction: 'Mention the umbrella.' },
|
|
13
14
|
]
|
|
14
15
|
|
|
15
16
|
/** The plugin ignores the run it is handed; a spec has none outside a loop. */
|
|
16
17
|
const NO_RUN = {} as AgentRun
|
|
17
18
|
|
|
18
19
|
describe('@owlmeans/bot-agent — botAgentPlugin', () => {
|
|
19
|
-
test('contributes the message, chat, sender, replied-to text
|
|
20
|
+
test('contributes the message, chat, sender, replied-to text and the instructions the handler passes', async () => {
|
|
20
21
|
const bot = makeTestBot()
|
|
21
22
|
const { update, actions } = await receive(bot, {
|
|
22
23
|
text: 'Free money at the CASINO, idiot!\nJoin now',
|
|
23
24
|
chat: { title: 'Gamers' },
|
|
24
25
|
sender: { id: '777', name: 'Spammer', username: 'spammer' },
|
|
25
26
|
replyTo: { id: '5', text: 'Welcome, everyone' },
|
|
26
|
-
}
|
|
27
|
+
})
|
|
27
28
|
|
|
28
|
-
const
|
|
29
|
-
const
|
|
29
|
+
const matched = matchedGroups(update.message?.text ?? '', GROUPS)
|
|
30
|
+
const plugin = botAgentPlugin({
|
|
31
|
+
update, bot: actions, ref: matched[0].id,
|
|
32
|
+
instruction: [...matched.map(group => group.instruction ?? ''), 'Never ban anybody.'],
|
|
33
|
+
})
|
|
34
|
+
const [message, instruction, ...rest] = await plugin.context!(NO_RUN)
|
|
30
35
|
|
|
31
36
|
expect(rest).toEqual([])
|
|
32
37
|
expect(message).toContain('Platform: Telegram')
|
|
@@ -34,16 +39,15 @@ describe('@owlmeans/bot-agent — botAgentPlugin', () => {
|
|
|
34
39
|
expect(message).toContain('Sender: Spammer (@spammer)')
|
|
35
40
|
expect(message).toContain('> Free money at the CASINO, idiot!\n> Join now')
|
|
36
41
|
expect(message).toContain('> Welcome, everyone')
|
|
37
|
-
expect(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
expect(
|
|
41
|
-
expect(instruction).toBe('# Instruction\n\nNever ban anybody.')
|
|
42
|
+
expect(instruction).toBe('# Instruction\n\nDelete advertising.\n\nWarn politely.\n\nNever ban anybody.')
|
|
43
|
+
expect(instruction).not.toContain('umbrella')
|
|
44
|
+
// No instruction: the message alone.
|
|
45
|
+
expect(await botAgentPlugin({ update, bot: actions }).context!(NO_RUN)).toEqual([message])
|
|
42
46
|
})
|
|
43
47
|
|
|
44
48
|
test('offers only the tools the platform supports and allow names', async () => {
|
|
45
49
|
const bot = makeTestBot()
|
|
46
|
-
const telegram = await receive(bot, { text: 'casino' }
|
|
50
|
+
const telegram = await receive(bot, { text: 'casino' })
|
|
47
51
|
const whatsApp = await whatsAppUpdate(bot)
|
|
48
52
|
const names = (options: Parameters<typeof botAgentPlugin>[0]): string[] =>
|
|
49
53
|
Object.values(botAgentPlugin(options).tools!(NO_RUN)).map(item => item.name)
|
package/tests/tools.spec.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test'
|
|
2
2
|
import { toolHelper } from '@owlmeans/agent'
|
|
3
|
-
import { BotActionKind,
|
|
3
|
+
import { BotActionKind, BotTransport } from '@owlmeans/bot'
|
|
4
4
|
import { telegramBotPlatform } from '@owlmeans/server-bot'
|
|
5
5
|
import { BOT_AGENT_DONE, BotAgentTool, botAgentTools } from '../src/index.js'
|
|
6
6
|
import { makeTestBot, receive } from './context.js'
|
|
@@ -9,12 +9,12 @@ const callOf = (name: string, args: Record<string, unknown> = {}) =>
|
|
|
9
9
|
({ name, args, id: `call_${name}`, type: 'tool_call' as const })
|
|
10
10
|
|
|
11
11
|
describe('@owlmeans/bot-agent — botAgentTools', () => {
|
|
12
|
-
test('each tool performs its action on the update and answers done', async () => {
|
|
12
|
+
test('each tool performs its action on the update, records the ref, and answers done', async () => {
|
|
13
13
|
const bot = makeTestBot()
|
|
14
|
-
const { connection, update, actions } = await receive(bot, { text: 'casino tonight', sender: { id: '777' } }
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const tools = botAgentTools({ update, bot: actions })
|
|
14
|
+
const { connection, update, actions } = await receive(bot, { text: 'casino tonight', sender: { id: '777' } })
|
|
15
|
+
// The id of the application's own keyword group the handler matched.
|
|
16
|
+
const ref = 'group-spam'
|
|
17
|
+
const tools = botAgentTools({ update, bot: actions, ref })
|
|
18
18
|
|
|
19
19
|
for (const [name, args] of [
|
|
20
20
|
[BotAgentTool.Reply, { text: 'Hello' }], [BotAgentTool.Delete, {}], [BotAgentTool.Warn, { text: 'No ads, please.' }],
|
|
@@ -25,16 +25,15 @@ describe('@owlmeans/bot-agent — botAgentTools', () => {
|
|
|
25
25
|
|
|
26
26
|
const message = { chat: update.chat.id, message: update.message!.ref.id }
|
|
27
27
|
const member = { chat: update.chat.id, user: '777' }
|
|
28
|
-
const rule = update.matches[0].rule.id
|
|
29
28
|
const activity = (await bot.service.activity.list(bot.scope)).reverse()
|
|
30
|
-
expect(activity.map(entry => [entry.action, entry.ok, entry.update, entry.
|
|
29
|
+
expect(activity.map(entry => [entry.action, entry.ok, entry.update, entry.ref, entry.target, entry.text ?? null]))
|
|
31
30
|
.toEqual([
|
|
32
|
-
[BotActionKind.Reply, true, update.id,
|
|
33
|
-
[BotActionKind.Delete, true, update.id,
|
|
34
|
-
[BotActionKind.Reply, true, update.id,
|
|
35
|
-
[BotActionKind.Mute, true, update.id,
|
|
36
|
-
[BotActionKind.Ban, true, update.id,
|
|
37
|
-
[BotActionKind.React, true, update.id,
|
|
31
|
+
[BotActionKind.Reply, true, update.id, ref, message, 'Hello'],
|
|
32
|
+
[BotActionKind.Delete, true, update.id, ref, message, null],
|
|
33
|
+
[BotActionKind.Reply, true, update.id, ref, message, 'No ads, please.'],
|
|
34
|
+
[BotActionKind.Mute, true, update.id, ref, member, null],
|
|
35
|
+
[BotActionKind.Ban, true, update.id, ref, member, null],
|
|
36
|
+
[BotActionKind.React, true, update.id, ref, message, '👎'],
|
|
38
37
|
])
|
|
39
38
|
expect(activity.every(entry => entry.transport === BotTransport.Mock)).toBe(true)
|
|
40
39
|
expect(telegramBotPlatform.mock.calls(connection).map(call => call.method)).toEqual(expect.arrayContaining([
|