@matatbread/matbot-plugin-api 0.4.16 → 0.4.17

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/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "node",
5
5
  "browser"
6
6
  ],
7
- "version": "0.4.16",
7
+ "version": "0.4.17",
8
8
  "engines": {
9
9
  "node": ">=24.0.0"
10
10
  },
package/src/hooks.ts CHANGED
@@ -20,11 +20,12 @@ export class HookRegistry implements HookRegistrar {
20
20
  private readonly markedFailures = new Set<Hook>();
21
21
  private pendingFailureMarkers: { channel: HookPoint; pluginName?: string; message: string }[] = [];
22
22
 
23
+ // A fresh array, never an in-place push + sort, for the reason given at `removeOne`: a run loop
24
+ // iterating the old list across its awaits would otherwise visit a hook registered mid-run, or visit
25
+ // one twice after the sort moved it behind the cursor.
23
26
  register(hook: Hook): void {
24
- const list = this.hooks.get(hook.on) ?? [];
25
- list.push(hook);
26
- list.sort((a, b) => (a.priority ?? 50) - (b.priority ?? 50));
27
- this.hooks.set(hook.on, list);
27
+ const list = [...(this.hooks.get(hook.on) ?? []), hook];
28
+ this.hooks.set(hook.on, list.sort((a, b) => (a.priority ?? 50) - (b.priority ?? 50)));
28
29
  }
29
30
 
30
31
  // The hooks registered on one channel, narrowed to that channel's arm. The map is keyed BY `on`, so
@@ -2,6 +2,7 @@ import type {
2
2
  CompletionRequest, MatbotMachine, MatbotServices, Mounted, MountConsumeOptions, SingleTurnRequest,
3
3
  } from './plugin.js';
4
4
  import { RegistryChangeKind } from './notify.js';
5
+ import { createMessage } from './session.js';
5
6
 
6
7
  /*
7
8
  * Host-side machine assembly — the wiring an *embedder* does once at boot, not API a plugin author
@@ -205,16 +206,17 @@ export function createMountTable(getMachine: () => MatbotMachine): MountTable {
205
206
 
206
207
  /**
207
208
  * Build the one-message CompletionRequest for a {@link MatbotRuntime.singleTurn} call, hiding the
208
- * otherwise-mandatory and meaningless Message fields (id/traceId/createdAt) an out-of-band one-shot
209
- * has no use for. Pure; the host invokes its own complete() with the result.
209
+ * otherwise-mandatory Message fields an out-of-band one-shot has no use for. The host invokes its own
210
+ * complete() with the result.
210
211
  */
211
212
  export function singleTurnRequest(req: SingleTurnRequest): CompletionRequest {
212
213
  return {
213
214
  provider: req.provider,
214
- messages: [{
215
- id: '', traceId: '', createdAt: new Date().toISOString(), role: 'user',
215
+ messages: [createMessage({
216
+ role: 'user',
217
+ traceId: '',
216
218
  content: typeof req.prompt === 'string' ? [{ type: 'text', text: req.prompt }] : req.prompt,
217
- }],
219
+ })],
218
220
  ...(req.system !== undefined ? { system: req.system } : {}),
219
221
  ...(req.parameters !== undefined ? { parameters: req.parameters } : {}),
220
222
  ...(req.signal !== undefined ? { signal: req.signal } : {}),
package/src/notify.ts CHANGED
@@ -193,6 +193,7 @@ export interface Notifier {
193
193
  */
194
194
  export function createNotifier(defaultPlugin: string): Notifier {
195
195
  const events = createBroadcaster<Notification>();
196
+ const warned = new Set<string>();
196
197
  return {
197
198
  notify(notification) {
198
199
  const plugin = notification.plugin ?? defaultPlugin;
@@ -200,9 +201,12 @@ export function createNotifier(defaultPlugin: string): Notifier {
200
201
  // matter most here: a plain-JS plugin, or a bridge injecting another instance's traffic. Warn, don't
201
202
  // throw — `notify` is contractually non-throwing, and a badly-named kind is a hygiene fault, not a
202
203
  // delivery failure. Only the shape is checkable; that the prefix names the *definer* is not, since
203
- // the emitter legitimately differs from it.
204
- if (!notification.kind.includes('#')) {
205
- console.warn(`[matbot] ${plugin} published unqualified notification kind "${notification.kind}" — expected <package-name>#<InterfaceName>`);
204
+ // the emitter legitimately differs from it. Once per emitter and kind: the fault is in the source,
205
+ // so repeating it on every publish only buries the log.
206
+ const warning = `${plugin} published unqualified notification kind "${notification.kind}"`;
207
+ if (!notification.kind.includes('#') && !warned.has(warning)) {
208
+ warned.add(warning);
209
+ console.warn(`[matbot] ${warning} — expected <package-name>#<InterfaceName>`);
206
210
  }
207
211
  events.emit({ ...notification, plugin } as Notification);
208
212
  },
@@ -116,8 +116,7 @@ function isAsyncIterator(value: unknown): value is AsyncIterator<unknown> & Asyn
116
116
  && typeof (value as AsyncIterator<unknown>).next === 'function';
117
117
  }
118
118
 
119
- // The per-pull re-entry itself is `scopeIterator` (./scoped-iterator.ts) — the usage carrier needs the
120
- // identical wrap for its call site, and had its own, less faithful copy.
119
+ // The per-pull re-entry itself is `scopeIterator` (./scoped-iterator.ts), shared with the usage carrier.
121
120
  function scopedIterator<T>(
122
121
  carrier: PrincipalCarrier,
123
122
  principal: Principal,