@matatbread/matbot-triggers 0.2.5 → 0.2.7

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.
Files changed (2) hide show
  1. package/package.json +4 -4
  2. package/src/tools.ts +86 -20
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "node",
5
5
  "browser"
6
6
  ],
7
- "version": "0.2.5",
7
+ "version": "0.2.7",
8
8
  "engines": {
9
9
  "node": ">=24.0.0"
10
10
  },
@@ -33,11 +33,11 @@
33
33
  "access": "public"
34
34
  },
35
35
  "peerDependencies": {
36
- "@matatbread/matbot-plugin-api": "^0.2.5"
36
+ "@matatbread/matbot-plugin-api": "^0.2.7"
37
37
  },
38
38
  "devDependencies": {
39
- "typescript": "^5.8.0",
40
- "@matatbread/matbot-plugin-api": "^0.2.5"
39
+ "typescript": "^6.0.3",
40
+ "@matatbread/matbot-plugin-api": "^0.2.7"
41
41
  },
42
42
  "scripts": {
43
43
  "typecheck": "tsc --noEmit"
package/src/tools.ts CHANGED
@@ -1,6 +1,26 @@
1
- import type { Tool, ToolExecutor, ToolContext, ToolEvent, MatbotMachine } from '@matatbread/matbot-plugin-api';
1
+ import type { Tool, ToolExecutor, ToolResult, ToolResultOf, ToolContext, MatbotMachine } from '@matatbread/matbot-plugin-api';
2
2
  import type { TriggerManager } from './manager.js';
3
- import type { TriggerCondition, TriggerKind } from './types.js';
3
+ import type { TriggerCondition, TriggerKind, Trigger } from './types.js';
4
+
5
+ declare module '@matatbread/matbot-plugin-api' {
6
+ interface ToolResults {
7
+ // One arm per action: a caller of `invokeTool(machine, 'trigger_action', { action: '…' })` gets the
8
+ // matching result narrowed by the `action` it passed (see ToolResult / the multi-action note on ToolResults).
9
+ trigger_action:
10
+ | ToolResult<{ triggers: Trigger[] }, { action: 'list' }>
11
+ | ToolResult<{ triggers: Trigger[] }, { action: 'query' }>
12
+ | ToolResult<Trigger, { action: 'get' }>
13
+ | ToolResult<{ id: string }, { action: 'add' }>
14
+ | ToolResult<Trigger, { action: 'update' }>
15
+ | ToolResult<{ id: string }, { action: 'remove' }>
16
+ | ToolResult<{ triggers: Trigger[] }, { action: 'move' }>
17
+ | ToolResult<{ triggers: Trigger[] }, { action: 'copy' }>;
18
+ triggers_config:
19
+ | ToolResult<{ classifierProvider: string | null; available: string[] }, { action: 'get' }>
20
+ | ToolResult<{ classifierProvider: string }, { action: 'set' }>
21
+ | ToolResult<{ classifierProvider: null }, { action: 'clear' }>;
22
+ }
23
+ }
4
24
 
5
25
  const KINDS: readonly TriggerKind[] = ['ephemeral', 'contextual', 'retract', 'followup'];
6
26
  const isKind = (x: unknown): x is TriggerKind => typeof x === 'string' && (KINDS as readonly string[]).includes(x);
@@ -39,7 +59,17 @@ type TriggerActionInput =
39
59
  | { action: 'get'; id: string }
40
60
  | { action: 'add'; conditions: TriggerCondition[]; tool: string; params?: unknown; enabled?: boolean }
41
61
  | { action: 'update'; id: string; conditions?: TriggerCondition[]; tool?: string; params?: unknown; enabled?: boolean }
42
- | { action: 'remove'; id: string };
62
+ | { action: 'remove'; id: string }
63
+ | { action: 'move'; tool?: string; params?: unknown; toTool: string; toParams?: unknown }
64
+ | { action: 'copy'; tool?: string; params?: unknown; toTool: string; toParams?: unknown };
65
+
66
+ // The query selector shared by query/move/copy: `tool`/`params` narrow the set by what a trigger invokes.
67
+ function queryFilter(a: { tool?: unknown; params?: unknown }): { tool?: string; params?: unknown } {
68
+ return {
69
+ ...(typeof a.tool === 'string' ? { tool: a.tool } : {}),
70
+ ...(a.params !== undefined ? { params: a.params } : {}),
71
+ };
72
+ }
43
73
 
44
74
  // A condition is valid if it has a recognised `kind` and a string `rule`.
45
75
  function validConditions(x: unknown): x is TriggerCondition[] {
@@ -49,9 +79,9 @@ function validConditions(x: unknown): x is TriggerCondition[] {
49
79
  typeof (c as { rule?: unknown }).rule === 'string');
50
80
  }
51
81
 
52
- export function createTriggerActionTool(manager: TriggerManager): Tool {
53
- const executor: ToolExecutor = {
54
- async *execute(input: unknown, _ctx: ToolContext): AsyncIterable<ToolEvent> {
82
+ export function createTriggerActionTool(manager: TriggerManager): Tool<ToolResultOf<'trigger_action'>> {
83
+ const executor: ToolExecutor<ToolResultOf<'trigger_action'>> = {
84
+ async *execute(input: unknown, _ctx: ToolContext) {
55
85
  const args = input as Partial<TriggerActionInput> & { action?: string };
56
86
 
57
87
  switch (args.action) {
@@ -62,10 +92,7 @@ export function createTriggerActionTool(manager: TriggerManager): Tool {
62
92
 
63
93
  case 'query': {
64
94
  const a = args as Extract<TriggerActionInput, { action: 'query' }>;
65
- yield { type: 'result', value: { triggers: manager.query({
66
- ...(typeof a.tool === 'string' ? { tool: a.tool } : {}),
67
- ...(a.params !== undefined ? { params: a.params } : {}),
68
- }) } };
95
+ yield { type: 'result', value: { triggers: manager.query(queryFilter(a)) } };
69
96
  return;
70
97
  }
71
98
 
@@ -118,8 +145,39 @@ export function createTriggerActionTool(manager: TriggerManager): Tool {
118
145
  return;
119
146
  }
120
147
 
148
+ case 'move': {
149
+ const a = args as Extract<TriggerActionInput, { action: 'move' }>;
150
+ if (typeof a.toTool !== 'string') { yield { type: 'error', message: 'action "move" requires a string "toTool" to re-target the selected triggers onto.' }; return; }
151
+ if (a.tool === undefined && a.params === undefined) { yield { type: 'error', message: 'action "move" requires a filter ("tool" and/or "params") selecting which triggers to re-target — refusing to move every trigger.' }; return; }
152
+ const invoke = { tool: a.toTool, ...(a.toParams !== undefined ? { params: a.toParams } : {}) };
153
+ const triggers: Trigger[] = [];
154
+ for (const t of manager.query(queryFilter(a))) {
155
+ const updated = await manager.update(t.id, { invoke });
156
+ if (updated !== undefined) triggers.push(updated);
157
+ }
158
+ yield { type: 'result', value: { triggers } };
159
+ return;
160
+ }
161
+
162
+ case 'copy': {
163
+ const a = args as Extract<TriggerActionInput, { action: 'copy' }>;
164
+ if (typeof a.toTool !== 'string') { yield { type: 'error', message: 'action "copy" requires a string "toTool" to point the duplicates at.' }; return; }
165
+ if (a.tool === undefined && a.params === undefined) { yield { type: 'error', message: 'action "copy" requires a filter ("tool" and/or "params") selecting which triggers to duplicate — refusing to duplicate every trigger.' }; return; }
166
+ const invoke = { tool: a.toTool, ...(a.toParams !== undefined ? { params: a.toParams } : {}) };
167
+ const triggers: Trigger[] = [];
168
+ for (const t of manager.query(queryFilter(a))) {
169
+ triggers.push(await manager.add({
170
+ conditions: t.conditions,
171
+ invoke,
172
+ ...(t.enabled !== undefined ? { enabled: t.enabled } : {}),
173
+ }));
174
+ }
175
+ yield { type: 'result', value: { triggers } };
176
+ return;
177
+ }
178
+
121
179
  default:
122
- yield { type: 'error', message: `Unknown action "${String(args.action)}". Expected one of: list, query, get, add, update, remove.` };
180
+ yield { type: 'error', message: `Unknown action "${String(args.action)}". Expected one of: list, query, get, add, update, remove, move, copy.` };
123
181
  }
124
182
  },
125
183
  };
@@ -129,7 +187,8 @@ export function createTriggerActionTool(manager: TriggerManager): Tool {
129
187
  description:
130
188
  'Manage triggers — data-driven hooks that invoke a tool when an LLM classifier judges one of ' +
131
189
  'their conditions matched against the current turn. Use this to list triggers, find the ones ' +
132
- 'that invoke a given tool (query), read one, create one, edit one by id, or delete one.\n\n' +
190
+ 'that invoke a given tool (query), read one, create one, edit one by id, delete one, or bulk ' +
191
+ 're-target a selected set onto a new tool — in place (move) or as duplicates (copy).\n\n' +
133
192
  GUIDANCE + '\n\n' +
134
193
  'A trigger has a stable `id` — address it by that, never by its conditions. To change one, ' +
135
194
  '"get" or "list" first to read the id, then "update" by id.\n\n' +
@@ -143,18 +202,25 @@ export function createTriggerActionTool(manager: TriggerManager): Tool {
143
202
  " | { action: 'get'; id: string } // -> the trigger\n" +
144
203
  " | { action: 'add'; conditions: TriggerCondition[]; tool: string; params?: object; enabled?: boolean } // -> { id }\n" +
145
204
  " | { action: 'update'; id: string; conditions?: TriggerCondition[]; tool?: string; params?: object; enabled?: boolean } // edit by id\n" +
146
- " | { action: 'remove'; id: string }; // -> { id }\n" +
147
- '```',
205
+ " | { action: 'remove'; id: string } // -> { id }\n" +
206
+ " | { action: 'move'; tool?: string; params?: object; toTool: string; toParams?: object } // re-target the queried set's invoke to toTool/toParams in place -> { triggers: [...] }\n" +
207
+ " | { action: 'copy'; tool?: string; params?: object; toTool: string; toParams?: object }; // duplicate the queried set, the copies invoking toTool/toParams -> { triggers: [...] }\n" +
208
+ '```\n\n' +
209
+ 'For `move`/`copy`, `tool`/`params` are the SAME selector as `query` (they pick which triggers ' +
210
+ 'to act on); `toTool`/`toParams` are the new invocation. A filter is required — both refuse to ' +
211
+ 'act on the entire trigger set.',
148
212
  inputSchema: {
149
213
  type: 'object',
150
214
  required: ['action'],
151
215
  properties: {
152
- action: { type: 'string', enum: ['list', 'query', 'get', 'add', 'update', 'remove'], description: 'The operation to perform.' },
216
+ action: { type: 'string', enum: ['list', 'query', 'get', 'add', 'update', 'remove', 'move', 'copy'], description: 'The operation to perform.' },
153
217
  id: { type: 'string', description: 'Trigger id. Required for get/update/remove.' },
154
218
  conditions: { type: 'array', description: 'Conditions [{ kind: "ephemeral"|"contextual"|"retract"|"followup", rule: string }]. Required for add.',
155
219
  items: { type: 'object', properties: { kind: { type: 'string', enum: ['ephemeral', 'contextual', 'retract', 'followup'] }, rule: { type: 'string' } } } },
156
- tool: { type: 'string', description: 'Name of the tool to invoke when a condition matches. Required for add.' },
157
- params: { type: 'object', description: 'Params passed verbatim as the invoked tool\'s input.' },
220
+ tool: { type: 'string', description: 'For add: the tool to invoke. For query/move/copy: the selector matching invoke.tool.' },
221
+ params: { type: 'object', description: 'For add: params passed verbatim as the invoked tool\'s input. For query/move/copy: the selector matching invoke.params.' },
222
+ toTool: { type: 'string', description: 'New tool the selected triggers should invoke. Required for move/copy.' },
223
+ toParams: { type: 'object', description: 'New params for the selected triggers\' invocation (move/copy).' },
158
224
  enabled: { type: 'boolean', description: 'Set false to keep but disable the trigger.' },
159
225
  },
160
226
  },
@@ -168,10 +234,10 @@ export function createTriggerActionTool(manager: TriggerManager): Tool {
168
234
  * own provider (so triggers work with zero config); set it to pin a small/fast model. Resolved per
169
235
  * evaluation, so a change takes effect on the next turn without a restart.
170
236
  */
171
- export function createTriggersConfigTool(services: MatbotMachine): Tool {
237
+ export function createTriggersConfigTool(services: MatbotMachine): Tool<ToolResultOf<'triggers_config'>> {
172
238
  const KEY = 'classifierProvider';
173
- const executor: ToolExecutor = {
174
- async *execute(input: unknown, _ctx: ToolContext): AsyncIterable<ToolEvent> {
239
+ const executor: ToolExecutor<ToolResultOf<'triggers_config'>> = {
240
+ async *execute(input: unknown, _ctx: ToolContext) {
175
241
  const args = input as { action?: string; provider?: string };
176
242
  const settings = services.settings();
177
243
  const available = [...services.providers.keys()];