@matatbread/matbot-plugin-api 0.3.4 → 0.3.6
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 +1 -1
- package/src/hooks.ts +19 -10
- package/src/plugin.ts +35 -12
- package/src/types.ts +109 -1
package/package.json
CHANGED
package/src/hooks.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
Hook, HookPoint, Message, MessageContent, Session,
|
|
2
|
+
Hook, HookPoint, Message, MessageContent, Session, DeferredScreen,
|
|
3
3
|
ScreenContext, ContributeContext, ToolCallContext, ToolCallResult, ToolResultContext, FollowupContext,
|
|
4
4
|
} from './types.js';
|
|
5
5
|
|
|
@@ -88,9 +88,12 @@ export class HookRegistry {
|
|
|
88
88
|
|
|
89
89
|
// screen folds across hooks: each sees the session as shaped so far, accumulates ephemeral, and
|
|
90
90
|
// the first `abort` short-circuits (the partial session is returned so the caller can persist it).
|
|
91
|
-
async runScreen(ctx: Omit<ScreenContext, 'removeHook'>): Promise<{ session: Session; ephemeral: MessageContent[]; durable: MessageContent[]; markers: MessageContent[]; abort?: string }> {
|
|
91
|
+
async runScreen(ctx: Omit<ScreenContext, 'removeHook'>): Promise<{ session: Session; ephemeral: MessageContent[]; durable: MessageContent[]; markers: MessageContent[]; deferred: DeferredScreen[]; abort?: string }> {
|
|
92
92
|
let session = ctx.session;
|
|
93
93
|
const ephemeral: MessageContent[] = [];
|
|
94
|
+
// Raced verdicts handed back by hooks: the runner polls each without gating the turn (see
|
|
95
|
+
// DeferredScreen). Usually zero or one; an array so several racing hooks compose.
|
|
96
|
+
const deferred: DeferredScreen[] = [];
|
|
94
97
|
// Handler-returned markers: appended to the session here (so they persist) and accumulated so the
|
|
95
98
|
// runner can also emit them live — keeping a live draw and a reload identical.
|
|
96
99
|
const handlerMarkers: MessageContent[] = [];
|
|
@@ -122,13 +125,14 @@ export class HookRegistry {
|
|
|
122
125
|
if (r.ephemeral) ephemeral.push(...r.ephemeral);
|
|
123
126
|
if (r.durable && r.durable.length) foldDurable(r.durable);
|
|
124
127
|
if (r.markers && r.markers.length) appendMarkers(r.markers);
|
|
128
|
+
if (r.deferred) deferred.push(r.deferred);
|
|
125
129
|
if (r.abort) {
|
|
126
130
|
const drained = this.drainFailureMarkers(session);
|
|
127
|
-
return { session: drained.session, ephemeral, durable, markers: [...handlerMarkers, ...drained.markers], abort: r.abort };
|
|
131
|
+
return { session: drained.session, ephemeral, durable, markers: [...handlerMarkers, ...drained.markers], deferred, abort: r.abort };
|
|
128
132
|
}
|
|
129
133
|
}
|
|
130
134
|
const drained = this.drainFailureMarkers(session);
|
|
131
|
-
return { session: drained.session, ephemeral, durable, markers: [...handlerMarkers, ...drained.markers] };
|
|
135
|
+
return { session: drained.session, ephemeral, durable, markers: [...handlerMarkers, ...drained.markers], deferred };
|
|
132
136
|
}
|
|
133
137
|
|
|
134
138
|
// contribute folds the outgoing array through each hook — a pure transform pipeline; the stored
|
|
@@ -169,17 +173,22 @@ export class HookRegistry {
|
|
|
169
173
|
// turn), any durable markers (appended to the committed session by the pump), and any
|
|
170
174
|
// retract-and-rerun requests. A turn can be popped only once, so multiple hooks' retraction
|
|
171
175
|
// contexts merge into a single redo (in practice only one fires); `retract` is omitted when none did.
|
|
172
|
-
async runFollowup(ctx: Omit<FollowupContext, 'removeHook'>): Promise<{ resubmits: MessageContent[][]; markers: MessageContent[]; retract?: { context: MessageContent[] } }> {
|
|
173
|
-
const resubmits:
|
|
174
|
-
const markers:
|
|
175
|
-
const
|
|
176
|
+
async runFollowup(ctx: Omit<FollowupContext, 'removeHook'>): Promise<{ resubmits: MessageContent[][]; markers: MessageContent[]; retract?: { context: MessageContent[]; durable: MessageContent[] } }> {
|
|
177
|
+
const resubmits: MessageContent[][] = [];
|
|
178
|
+
const markers: MessageContent[] = [];
|
|
179
|
+
const retractCtx: MessageContent[] = [];
|
|
180
|
+
const retractDur: MessageContent[] = [];
|
|
176
181
|
for (const hook of this.hooks.get('followup') ?? []) {
|
|
177
182
|
if (hook.on !== 'followup') continue;
|
|
178
183
|
const r = await this.invoke(hook, () => hook.handler({ ...ctx, removeHook: () => this.removeOne('followup', hook) }));
|
|
179
184
|
if (r?.resubmit) resubmits.push(r.resubmit.content);
|
|
180
|
-
if (r?.retractAndRerun)
|
|
185
|
+
if (r?.retractAndRerun) {
|
|
186
|
+
if (r.retractAndRerun.context) retractCtx.push(...r.retractAndRerun.context);
|
|
187
|
+
if (r.retractAndRerun.durable) retractDur.push(...r.retractAndRerun.durable);
|
|
188
|
+
}
|
|
181
189
|
if (r?.markers) markers.push(...r.markers);
|
|
182
190
|
}
|
|
183
|
-
|
|
191
|
+
const retracting = retractCtx.length > 0 || retractDur.length > 0;
|
|
192
|
+
return { resubmits, markers, ...(retracting ? { retract: { context: retractCtx, durable: retractDur } } : {}) };
|
|
184
193
|
}
|
|
185
194
|
}
|
package/src/plugin.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
FileStore,
|
|
2
|
+
FileStore, StoreChange, Principal, Vault, Message, ModelParameters,
|
|
3
3
|
ProviderAdapter, ProviderConfig, ProviderRegistry, Tool, ToolRegistry, FrontendInfo,
|
|
4
4
|
Store, Session, SystemContextRegistry, KnowledgeIndex, PromptFn, SessionRunner, Usage,
|
|
5
|
-
TypeScriptStripper, ToolTypeIndex, ToolPresenter,
|
|
5
|
+
TypeScriptStripper, ToolTypeIndex, ToolPresenter, SteeringPolicy,
|
|
6
6
|
} from './types.js';
|
|
7
7
|
import type { Routed } from './broadcast.js';
|
|
8
8
|
import type { HookRegistry } from './hooks.js';
|
|
@@ -15,6 +15,15 @@ export interface CompletionRequest {
|
|
|
15
15
|
provider: string;
|
|
16
16
|
messages: Message[];
|
|
17
17
|
system?: string;
|
|
18
|
+
/**
|
|
19
|
+
* @deprecated Per-call parameter overrides, shallow-merged over the provider config (request wins).
|
|
20
|
+
* It DOES work — both hosts (CLI + web-bundle) honour it — but it is a speculative affordance whose
|
|
21
|
+
* obvious use is an anti-pattern: a caller clamping tokens / disabling thinking for one call takes
|
|
22
|
+
* control away from the provider config, where parameters belong and stay user-editable. Prefer a
|
|
23
|
+
* dedicated provider profile (e.g. a `-classify` provider) instead of reaching for this. Kept, not
|
|
24
|
+
* removed, only because the surface is tiny and a genuine per-call need may yet appear. If you found
|
|
25
|
+
* this because you set it and it worked — reconsider whether a provider profile is the right home.
|
|
26
|
+
*/
|
|
18
27
|
parameters?: Partial<ModelParameters>;
|
|
19
28
|
signal?: AbortSignal;
|
|
20
29
|
}
|
|
@@ -28,6 +37,9 @@ export interface SingleTurnRequest {
|
|
|
28
37
|
provider: string;
|
|
29
38
|
prompt: string;
|
|
30
39
|
system?: string;
|
|
40
|
+
/** @deprecated Forwarded verbatim to {@link CompletionRequest.parameters}; same speculative
|
|
41
|
+
* anti-pattern caveat — prefer a dedicated provider profile over per-call overrides. */
|
|
42
|
+
parameters?: Partial<ModelParameters>;
|
|
31
43
|
signal?: AbortSignal;
|
|
32
44
|
}
|
|
33
45
|
|
|
@@ -111,6 +123,11 @@ export interface MatbotServices {
|
|
|
111
123
|
* registered by a partitioning storage backend. Absent ⇒ frontends use the plain `fileStore.watch()`.
|
|
112
124
|
* See {@link WatchVisibility}. */
|
|
113
125
|
readonly WatchVisibility?: WatchVisibility | undefined;
|
|
126
|
+
/** Optional steering policy: decides how a mid-turn submission under `mode: 'auto'` is disposed
|
|
127
|
+
* (queue vs interrupt) and supplies the nudge folded onto an interrupt's continuation. Absent ⇒ the
|
|
128
|
+
* runner uses its own defaults. A plain registered service, consumed by the runner as a member.
|
|
129
|
+
* See {@link SteeringPolicy}. */
|
|
130
|
+
readonly SteeringPolicy?: SteeringPolicy | undefined;
|
|
114
131
|
}
|
|
115
132
|
|
|
116
133
|
/** The assembled machine: registry services wired to the fixed runtime — what `setup()` receives. */
|
|
@@ -450,8 +467,9 @@ export function singleTurnRequest(req: SingleTurnRequest): CompletionRequest {
|
|
|
450
467
|
id: '', traceId: '', createdAt: new Date().toISOString(), role: 'user',
|
|
451
468
|
content: [{ type: 'text', text: req.prompt }],
|
|
452
469
|
}],
|
|
453
|
-
...(req.system
|
|
454
|
-
...(req.
|
|
470
|
+
...(req.system !== undefined ? { system: req.system } : {}),
|
|
471
|
+
...(req.parameters !== undefined ? { parameters: req.parameters } : {}),
|
|
472
|
+
...(req.signal !== undefined ? { signal: req.signal } : {}),
|
|
455
473
|
};
|
|
456
474
|
}
|
|
457
475
|
|
|
@@ -494,18 +512,23 @@ export interface StorageBackend {
|
|
|
494
512
|
* The origin/visibility split is generic (files today; the same shape serves skills/stores later).
|
|
495
513
|
*/
|
|
496
514
|
export interface WatchVisibility {
|
|
497
|
-
/** Every partition's file events, merged into one stream, each
|
|
498
|
-
|
|
515
|
+
/** Every partition's file events, merged into one stream, each a self-describing {@link StoreChange}
|
|
516
|
+
* (routing namespace `'files'`, the file id, and the {@link FileMetaData} in `detail`) tagged with the
|
|
517
|
+
* origin partition — so the firehose filters and routes files through the same predicate as any stream. */
|
|
518
|
+
watchFiles(signal?: AbortSignal): AsyncIterable<Routed<StoreChange>>;
|
|
499
519
|
/**
|
|
500
520
|
* The per-connection visibility predicate for ANY partitioned event stream (files, skills, …): would a
|
|
501
|
-
* connection owned by `viewer` see
|
|
502
|
-
*
|
|
503
|
-
*
|
|
504
|
-
*
|
|
521
|
+
* connection owned by `viewer` see the change to (`namespace`, `id`) produced by `origin`? True iff the
|
|
522
|
+
* viewer routes that namespace to the same partition as `origin` — `route(viewer, namespace) ===
|
|
523
|
+
* route(origin, namespace)` — OR the item is shared INTO the viewer's partition (the owner edits a
|
|
524
|
+
* shared-in item: origin=owner ≠ the viewer's route, yet the viewer holds a live link to it). This yields
|
|
525
|
+
* the intended behavior uniformly: global/base events for namespaces the viewer has NOT isolated,
|
|
526
|
+
* own-partition events for those it has, plus live updates to items shared in from another partition.
|
|
505
527
|
* `origin` may be a partition principal (files, tagged by the merge) or the acting principal (skills,
|
|
506
|
-
* stamped at write) — routing both sides makes either correct. `undefined` origin ⇒ base.
|
|
528
|
+
* stamped at write) — routing both sides makes either correct. `undefined` origin ⇒ base. `id` is the
|
|
529
|
+
* store id of the changed item; it drives the shared-in check.
|
|
507
530
|
*/
|
|
508
|
-
visible(viewer: Principal, namespace: string, origin: Principal | undefined): boolean;
|
|
531
|
+
visible(viewer: Principal, namespace: string, id: string, origin: Principal | undefined): boolean;
|
|
509
532
|
}
|
|
510
533
|
|
|
511
534
|
// ── Plugin interface ──────────────────────────────────────────────────────────
|
package/src/types.ts
CHANGED
|
@@ -310,8 +310,47 @@ export interface ScreenContext {
|
|
|
310
310
|
/** Unregister the hook currently running. For one-shot hooks that should fire at most once. */
|
|
311
311
|
removeHook(): void;
|
|
312
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* A raced screen verdict the runner folds in WITHOUT gating the turn on it. A `screen` hook that starts
|
|
315
|
+
* expensive work concurrently (e.g. a triggers classifier judging the user message) returns immediately
|
|
316
|
+
* and hands back one of these instead of blocking on it. The runner polls `claim()` — synchronously,
|
|
317
|
+
* never awaiting — at each turn-loop edge (before a provider call, on every stream event, and just
|
|
318
|
+
* before committing). The first time `claim()` returns blocks (the work has settled WITH a correction),
|
|
319
|
+
* the runner DISCARDS the uncommitted in-progress response and re-runs the loop with those blocks
|
|
320
|
+
* tail-folded as ephemeral context — an in-situ redo: no store pop, no retraction marker, cleaner than a
|
|
321
|
+
* post-commit retract. Because the mid-stream poll runs before each event is emitted, a verdict faster
|
|
322
|
+
* than time-to-first-token is caught before any token reaches the frontend (the clean path); a slower
|
|
323
|
+
* one aborts the in-flight provider request, saving the unstreamed remainder. If it never fires before
|
|
324
|
+
* the turn commits, the turn commits normally and any correction is left to a post-commit `followup`.
|
|
325
|
+
*
|
|
326
|
+
* `claim()` MUST be exactly-once: return the correction on the first successful poll and `undefined`
|
|
327
|
+
* forever after (not settled, no correction, or already claimed). The hook uses that single delivery to
|
|
328
|
+
* coordinate with its own post-commit path — a claimed verdict is never also delivered by `followup`.
|
|
329
|
+
*/
|
|
330
|
+
export interface DeferredScreen {
|
|
331
|
+
claim(): DeferredCorrection | undefined;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* A claimed raced-verdict correction. `ephemeral` is tail-folded onto the re-run's outgoing messages
|
|
335
|
+
* and never persisted (the transient "for this answer only" twin); `durable` is folded onto the turn's
|
|
336
|
+
* user message — persisted, visible (the hook marks it `origin: 'robo'`), and carried live as a
|
|
337
|
+
* `robo-user` event — so it updates the conversation rather than informing one answer (a `contextual`
|
|
338
|
+
* trigger's semantics, preserved even though the verdict now lands mid-turn instead of before it). At
|
|
339
|
+
* least one is non-empty when returned.
|
|
340
|
+
*/
|
|
341
|
+
export interface DeferredCorrection {
|
|
342
|
+
ephemeral?: MessageContent[];
|
|
343
|
+
durable?: MessageContent[];
|
|
344
|
+
}
|
|
313
345
|
export interface ScreenResult {
|
|
314
346
|
session?: Session;
|
|
347
|
+
/**
|
|
348
|
+
* A raced verdict the runner folds in without gating on it (see {@link DeferredScreen}). Lets a
|
|
349
|
+
* screen hook race expensive work — a classifier — against generation: return immediately, hand this
|
|
350
|
+
* back, and the runner restarts the turn in-situ if the verdict fires before commit. A hook that
|
|
351
|
+
* would rather gate the turn (block until the verdict) just returns `ephemeral` as usual instead.
|
|
352
|
+
*/
|
|
353
|
+
deferred?: DeferredScreen;
|
|
315
354
|
/** Turn-scoped context appended onto the tail of this turn's outgoing messages (the freshest
|
|
316
355
|
* input the model reads), never persisted. At the tail, not a system prefix, so a directive
|
|
317
356
|
* keeps its salience and the cached system/history prefix stays stable across turns. */
|
|
@@ -401,8 +440,13 @@ export interface FollowupResult {
|
|
|
401
440
|
* defect that the injected context dissolves on the redo, so it won't re-fire; `resubmitDepth` (a
|
|
402
441
|
* redo carries parent+1) caps an ill-formed one. `resubmit` and `retractAndRerun` are independent
|
|
403
442
|
* capabilities — a single turn returning both is not expected, but both head-enqueue if it does.
|
|
443
|
+
*
|
|
444
|
+
* `context` is folded EPHEMERALLY onto the redo (for-this-answer-only). `durable` is instead folded
|
|
445
|
+
* onto the re-run's user message — persisted, visible (mark it `origin: 'robo'`), carried live as a
|
|
446
|
+
* `robo-user` event — so a `contextual`-kind correction updates the conversation durably even when it
|
|
447
|
+
* lands post-commit. At least one of the two is present.
|
|
404
448
|
*/
|
|
405
|
-
retractAndRerun?: { context
|
|
449
|
+
retractAndRerun?: { context?: MessageContent[]; durable?: MessageContent[] };
|
|
406
450
|
/**
|
|
407
451
|
* Durable `marker` blocks to append to the just-committed session (LLM-invisible; for tracing /
|
|
408
452
|
* cross-references). The second durable-write capability after `screen` — safe here for the same
|
|
@@ -738,6 +782,26 @@ export interface FileHandle extends FileMetaData {
|
|
|
738
782
|
|
|
739
783
|
export type FileEvent = FileMetaData & { changed: Array<keyof FileMetaData> };
|
|
740
784
|
|
|
785
|
+
/**
|
|
786
|
+
* The generic, self-describing change envelope every partitioned CRUD stream emits (files, skills, and
|
|
787
|
+
* future partitioned stores) — the payload half of a {@link Routed} event, whose `origin` carries the
|
|
788
|
+
* acting principal/partition. It supersedes the per-stream bespoke shapes (the old `SkillEvent`, the raw
|
|
789
|
+
* `FileEvent` on the wire) so a frontend firehose can filter and route every stream through ONE predicate.
|
|
790
|
+
*
|
|
791
|
+
* `namespace` is the ROUTING namespace the change lives under — `'files'`, `'skills'`, or a document
|
|
792
|
+
* namespace — NOT a content sub-namespace (a file's own `metadata.namespace`, e.g. `'workspace'`, rides in
|
|
793
|
+
* `detail`). Pairing `namespace` + `id` is exactly what `WatchVisibility.visible` needs to decide, per
|
|
794
|
+
* connection, whether the viewer routes that item to the same partition (or has it shared in). `detail`
|
|
795
|
+
* carries an optional rich payload for in-place UI updates — a file change ships its {@link FileMetaData}
|
|
796
|
+
* so a sidebar row can update its size without a re-fetch; a skill change needs none (a refresh ping).
|
|
797
|
+
*/
|
|
798
|
+
export interface StoreChange {
|
|
799
|
+
readonly operation: 'saved' | 'deleted';
|
|
800
|
+
readonly namespace: string;
|
|
801
|
+
readonly id: string;
|
|
802
|
+
readonly detail?: unknown;
|
|
803
|
+
}
|
|
804
|
+
|
|
741
805
|
export interface FileFilter {
|
|
742
806
|
sessionId?: string;
|
|
743
807
|
mimeType?: string;
|
|
@@ -930,6 +994,13 @@ export type PipelineEvent =
|
|
|
930
994
|
// threw), carried live so a frontend renders them without waiting for a session reload. The blocks
|
|
931
995
|
// are already persisted in the session; this event is purely the live-delivery channel.
|
|
932
996
|
| { type: 'marker'; content: MessageContent[]; traceId: string }
|
|
997
|
+
// A mid-turn steer that INTERRUPTED the running turn (see `SteeringPolicy`). Emitted synchronously
|
|
998
|
+
// when the steer is accepted, so the new user bubble lands in correct stream order and a frontend
|
|
999
|
+
// knows the imminent `aborted` (reason 'steer') on `interruptedTraceId` is a yield, not a dead-end:
|
|
1000
|
+
// keep that turn's partial work rendered and expect a continuation. `traceId`/`rootTraceId` identify
|
|
1001
|
+
// the steer submission itself (the continuation turn). Late subscribers reconstruct the bubble from
|
|
1002
|
+
// the pump's per-turn `queued` replay seed / committed store order — this is purely live delivery.
|
|
1003
|
+
| { type: 'steer'; content: MessageContent[]; interruptedTraceId: string; traceId: string; rootTraceId: string }
|
|
933
1004
|
| { type: 'error'; error: string; traceId: string }
|
|
934
1005
|
| { type: 'system-context'; text: string; traceId: string };
|
|
935
1006
|
|
|
@@ -968,6 +1039,11 @@ export interface SubmitOpenOpts extends OpenOpts {
|
|
|
968
1039
|
/** When true, this submission may be merged with others drained in the same batch. Default false
|
|
969
1040
|
* (queue mode: one turn per submission). */
|
|
970
1041
|
concatQueue?: boolean;
|
|
1042
|
+
/** Disposition for a submission arriving while a turn is running (see {@link SteeringPolicy}):
|
|
1043
|
+
* 'queue' waits for the turn boundary (default), 'interrupt' stops the running turn — keeping its
|
|
1044
|
+
* committed partial work — and runs this next, 'auto' defers to the registered SteeringPolicy (else
|
|
1045
|
+
* the host default). Meaningless when nothing is running (degrades to a plain enqueue). */
|
|
1046
|
+
mode?: SteeringMode;
|
|
971
1047
|
/** Interactive prompt implementation for this submission's turn. The frontend owns delivery —
|
|
972
1048
|
* it must target the frontend's per-session client connections, not a single request. */
|
|
973
1049
|
prompt?: PromptFn;
|
|
@@ -991,3 +1067,35 @@ export interface SessionRunner {
|
|
|
991
1067
|
* behind it. `busy` is `running || queued > 0`. */
|
|
992
1068
|
status(sessionId: string): { busy: boolean; running: boolean; queued: number };
|
|
993
1069
|
}
|
|
1070
|
+
|
|
1071
|
+
// ── Steering ────────────────────────────────────────────────────────────────────
|
|
1072
|
+
|
|
1073
|
+
/** Disposition of a submission that arrives while a turn is running. `interrupt` stops the running
|
|
1074
|
+
* turn (its committed partial work is preserved) and runs the new message next; `queue` waits for the
|
|
1075
|
+
* turn boundary. */
|
|
1076
|
+
export type SteeringDecision = 'queue' | 'interrupt';
|
|
1077
|
+
/** The wire-level request (see `SubmitOpenOpts.mode`). `auto` defers the decision to the registered
|
|
1078
|
+
* {@link SteeringPolicy} (else the host default). */
|
|
1079
|
+
export type SteeringMode = SteeringDecision | 'auto';
|
|
1080
|
+
|
|
1081
|
+
/**
|
|
1082
|
+
* How the runner disposes of a mid-turn submission under `mode: 'auto'`, and how an interrupt's
|
|
1083
|
+
* continuation is nudged. An optional, registerable service ({@link MatbotServices}); absent ⇒ the
|
|
1084
|
+
* runner uses its own defaults. Both members are optional so a plugin may override one, the other, or
|
|
1085
|
+
* both. `classify` is deliberately NOT assumed to be an LLM — a regex or a semantic classifier is a
|
|
1086
|
+
* first-class implementation (return synchronously); an LLM `singleTurn` returns a promise.
|
|
1087
|
+
*/
|
|
1088
|
+
export interface SteeringPolicy {
|
|
1089
|
+
/**
|
|
1090
|
+
* Consulted only for `mode: 'auto'` while a turn is running. `session` is the COMMITTED session
|
|
1091
|
+
* (history up to the running turn's start — the in-flight partial is not reachable at submit time);
|
|
1092
|
+
* `steer` is the incoming submission. Return synchronously (regex) or async (semantic / singleTurn).
|
|
1093
|
+
* Absent ⇒ the host default disposition.
|
|
1094
|
+
*/
|
|
1095
|
+
classify?(ctx: { session: Session; steer: MessageContent[] }): SteeringDecision | Promise<SteeringDecision>;
|
|
1096
|
+
/**
|
|
1097
|
+
* The ephemeral "keep going, noting the above" context folded onto an interrupt's continuation turn
|
|
1098
|
+
* (never persisted). Absent ⇒ the host default nudge.
|
|
1099
|
+
*/
|
|
1100
|
+
nudge?(ctx: { session: Session; steer: MessageContent[] }): MessageContent[];
|
|
1101
|
+
}
|