@owlmeans/bot-agent 0.1.18-rc.2 → 0.1.18-rc.3

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 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
- keyword groups it matched, with their instructions, become the agent's context; the bot's actions
6
- (`reply`, `delete`, `warn`, `mute`, `ban`, `react`) become its tools, limited to what the platform
7
- supports and the caller allows. A tool never throws: it answers `done`, or what went wrong.
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 actions are `@owlmeans/server-bot`'s
10
- `BotActions`, which record every call as activity.
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-rc.2
17
+ bun add @owlmeans/bot-agent@^0.1.18-rc.3
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
- if (update.matches.length === 0) return
29
- const agent = makeModeratorAgent() // your makeAgentModel(…) factory
30
- agent.use(botAgentPlugin({ update, bot, allow: [BotActionKind.Delete, BotActionKind.Reply] }))
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(update, extra?)`, `contextOf(update, instruction?)`, `promptOf(update)` |
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
 
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 3,
3
3
  "package": "@owlmeans/bot-agent",
4
- "version": "0.1.18-rc.2",
5
- "generatedAt": "2026-10-26T22:23:53.870Z",
4
+ "version": "0.1.18-rc.3",
5
+ "generatedAt": "2026-10-27T13:25:25.437Z",
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 matched keyword groups' instructions 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; botAgentTools gives the tools without the plugin; botAgentHelper turns an update into instructions, 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.
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-rc.2"` in `dependencies`, plus the `@owlmeans/agent` and
11
+ **Install:** `"@owlmeans/bot-agent": "^0.1.18-rc.3"` in `dependencies`, plus the `@owlmeans/agent` and
12
12
  `@langchain/core` **peers**
13
- **Contract:** `@owlmeans/bot` (updates, matches, action kinds); actions: `@owlmeans/server-bot`
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 stores, receives and sends
17
- nothing itself — every tool calls a `BotActions` method, which records one activity whatever happens.
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(update, extra?)` | The matched groups' instructions (trimmed, empties and repeats dropped) then `extra`, joined by a blank line; `''` when none |
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
- Three markdown blocks, in this order — they land in the agent's project message
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. `# Matched keyword groups` — per `update.matches`, highest priority first: name, the keywords that
42
- matched, the group's `Action:` (omitted for `agent`), its `Reply text:` and `Instruction:` when
43
- set. No match says "None".
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 `rule`, the FIRST matched group.
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 agent's persona (its `prompt.role`) says what
77
- kind of bot it is; it never restates or overrides what a group instructs.
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.instructionOf(update)` and `contextOf(update)`, then
85
- `bot.reply` the answer — no tools, no loop.
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` checks `llmConfigured` first and `bot.note`s the
93
- refusal, then `.use(botAgentPlugin({ update, bot }))` and invokes the agent with the update.
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
- if (update.matches.length === 0) return // only what a keyword group matched
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({ update, bot, allow: [BotActionKind.Delete, BotActionKind.Reply, BotActionKind.Mute] }))
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. Covered: the context blocks of a
169
- matched update; the tool set by `supports` / `allow` / the update's shape; each tool's action, update,
170
- rule and answer; refusals answered as the action's sentence; one alias; a full loop acting through
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, matches, platform support
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 matched groups' instructions, in match order — trimmed, empties and repeats dropped — then
6
- * `extra`; joined by a blank line. `''` when there is none. For a caller that asks ONE model call
7
- * (no tools) and needs the owner's guidance as a single text.
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: (update: BotUpdate, extra?: string) => string;
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), the matched keyword groups (name, matched
13
- * keywords, action, fixed reply, instruction) and, when given, `instruction`.
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;;;;OAIG;IACH,aAAa,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,MAAM,CAAA;IAC5D;;;;OAIG;IACH,SAAS,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,WAAW,CAAC,EAAE,MAAM,KAAK,MAAM,EAAE,CAAA;IAChE,iHAAiH;IACjH,QAAQ,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,MAAM,CAAA;CACxC"}
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,cA2HvC,CAAA;AAED,eAAO,MAAM,cAAc,gBAAyB,CAAA"}
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"}
@@ -1,4 +1,4 @@
1
- import { BOT_PLATFORMS, BotRuleAction, BotUpdateKind } from '@owlmeans/bot';
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 = (update, extra) => {
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 [...(update.matches ?? []).map(match => match.rule.instruction), extra]) {
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), groupsBlock(update)];
86
- const extra = typeof instruction === 'string' ? instruction.trim() : '';
87
- if (extra !== '') {
88
- blocks.push(`# Instruction\n\n${extra}`);
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":"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"}
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 — the matched keyword groups with their instructions, and `instruction` when given; it lands
8
- * in the agent's project message (`PromptBlock.Context`), never in the cached system prompt.
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
  *
@@ -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;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc,YAAa,eAAe,KAAG,WAKxD,CAAA"}
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 — the matched keyword groups with their instructions, and `instruction` when given; it lands
9
- * in the agent's project message (`PromptBlock.Context`), never in the cached system prompt.
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
  *
@@ -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;;;;;;;;;;;GAWG;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"}
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. The activity each action records names the first matched group as its rule.
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
@@ -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;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,YAAa,oBAAoB,KAAG,YAqE7D,CAAA"}
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. The activity each action records names the first matched group as its rule.
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 rule = update.matches?.[0]?.rule.id;
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);
@@ -1 +1 @@
1
- {"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
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
- /** One more instruction, contributed after the matched groups' — the product's own rule for this agent. */
19
- instruction?: string;
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
@@ -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;CACjC;AAED,MAAM,WAAW,eAAgB,SAAQ,oBAAoB;IAC3D,2GAA2G;IAC3G,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB"}
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-rc.2",
3
+ "version": "0.1.18-rc.3",
4
4
  "license": "MIT",
5
- "description": "The bridge between an OwlMeans chat bot and an @owlmeans/agent agent — botAgentPlugin contributes a bot update (platform, chat, sender, message, the matched keyword groups and their instructions) 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.",
5
+ "description": "The bridge between an OwlMeans chat bot and an @owlmeans/agent agent — 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,8 +23,8 @@
23
23
  }
24
24
  },
25
25
  "dependencies": {
26
- "@owlmeans/bot": "^0.1.18-rc.2",
27
- "@owlmeans/server-bot": "^0.1.18-rc.2"
26
+ "@owlmeans/bot": "^0.1.18-rc.3",
27
+ "@owlmeans/server-bot": "^0.1.18-rc.3"
28
28
  },
29
29
  "peerDependencies": {
30
30
  "@langchain/core": "^1.2.14",
@@ -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 matched groups' instructions, in match order — trimmed, empties and repeats dropped — then
7
- * `extra`; joined by a blank line. `''` when there is none. For a caller that asks ONE model call
8
- * (no tools) and needs the owner's guidance as a single text.
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: (update: BotUpdate, extra?: string) => string
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), the matched keyword groups (name, matched
14
- * keywords, action, fixed reply, instruction) and, when given, `instruction`.
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
  }
@@ -1,4 +1,4 @@
1
- import { BOT_PLATFORMS, BotRuleAction, BotUpdateKind, type BotSenderRef, type BotUpdate } from '@owlmeans/bot'
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'] = (update, extra) => {
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 [...(update.matches ?? []).map(match => match.rule.instruction), extra]) {
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), groupsBlock(update)]
111
- const extra = typeof instruction === 'string' ? instruction.trim() : ''
112
- if (extra !== '') {
113
- blocks.push(`# Instruction\n\n${extra}`)
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 — the matched keyword groups with their instructions, and `instruction` when given; it lands
12
- * in the agent's project message (`PromptBlock.Context`), never in the cached system prompt.
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. The activity each action records names the first matched group as its rule.
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 rule = update.matches?.[0]?.rule.id
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
- /** One more instruction, contributed after the matched groups' — the product's own rule for this agent. */
21
- instruction?: string
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, type BotRuleInput, type BotSimulateInput, type BotUpdate } from '@owlmeans/bot'
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, RES_BOT_RULE, RES_BOT_UPDATE, type BotActions,
7
- type BotActivityRecord, type BotConnectionRecord, type BotHandle, type BotRuleRecord, type BotService,
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 four bot resources as `@owlmeans/static-resource` stores (a fresh
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): the rules
53
- * saved, the message simulated on a mock connection and dispatched by the worker's runtime to
54
- * `handle` — the update and the actions a handler receives. `handle` runs inside the handler, as a
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, rules: BotRuleInput[] = [], handle?: BotHandle,
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' } }, [], undefined, BotPlatform.WhatsApp)
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)
@@ -1,31 +1,29 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
- import { BotChatKind, BotRuleAction } from '@owlmeans/bot'
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 matched groups\' instructions, then the extra one', async () => {
8
- const bot = makeTestBot()
9
- const { update } = await receive(bot, { text: 'casino idiot rain' }, [
10
- { name: 'Spam', keywords: ['casino'], action: BotRuleAction.Agent, instruction: ' Delete advertising. ', order: 1 },
11
- { name: 'Rude', keywords: ['idiot'], action: BotRuleAction.Agent, instruction: 'Delete advertising.', order: 2 },
12
- { name: 'Weather', keywords: ['rain'], action: BotRuleAction.Reply, reply: 'Umbrella.', order: 3 },
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(2)
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[1]).toContain('None — the message matched no keyword group')
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 () => {
@@ -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, BotRuleAction } from '@owlmeans/bot'
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
- const RULES = [
28
- { name: 'Spam', keywords: ['casino'], action: BotRuleAction.Delete, instruction: 'Delete advertising and warn the sender.', order: 1 },
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' } }, RULES, async (update, actions) => {
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: Delete advertising and warn the sender.')
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.rule, entry.text ?? null])).toEqual([
83
- [BotActionKind.Delete, true, received.update.id, rule, null],
84
- [BotActionKind.Reply, true, received.update.id, rule, 'Advertising is not allowed here.'],
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
 
@@ -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, BotRuleAction } from '@owlmeans/bot'
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
- const RULES = [
10
- { name: 'Spam', keywords: ['casino', 'free money'], action: BotRuleAction.Delete, instruction: 'Delete advertising.', order: 1 },
11
- { name: 'Rudeness', keywords: ['idiot'], action: BotRuleAction.Agent, instruction: 'Warn politely.', reply: 'Be kind.', order: 2 },
12
- { name: 'Weather', keywords: ['rain'], action: BotRuleAction.Reply, reply: 'Take an umbrella.', order: 3 },
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, every matched group and the instruction', async () => {
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
- }, RULES)
27
+ })
27
28
 
28
- const plugin = botAgentPlugin({ update, bot: actions, instruction: 'Never ban anybody.' })
29
- const [message, groups, instruction, ...rest] = await plugin.context!(NO_RUN)
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(groups).toContain('## 1. Spam\nKeywords matched: casino, free money\nAction: delete\nInstruction: Delete advertising.')
38
- // `agent` is the hand-over itself, not an action to tell the model about.
39
- expect(groups).toContain('## 2. Rudeness\nKeywords matched: idiot\nReply text: Be kind.\nInstruction: Warn politely.')
40
- expect(groups).not.toContain('Weather')
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' }, RULES)
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)
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
2
  import { toolHelper } from '@owlmeans/agent'
3
- import { BotActionKind, BotRuleAction, BotTransport } from '@owlmeans/bot'
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
- { name: 'Spam', keywords: ['casino'], action: BotRuleAction.Delete, order: 1 },
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.rule, entry.target, entry.text ?? null]))
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, rule, message, 'Hello'],
33
- [BotActionKind.Delete, true, update.id, rule, message, null],
34
- [BotActionKind.Reply, true, update.id, rule, message, 'No ads, please.'],
35
- [BotActionKind.Mute, true, update.id, rule, member, null],
36
- [BotActionKind.Ban, true, update.id, rule, member, null],
37
- [BotActionKind.React, true, update.id, rule, message, '👎'],
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([