@kernel.chat/kbot 3.99.20 → 3.99.22

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 (36) hide show
  1. package/README.md +11 -0
  2. package/dist/agent.js +23 -0
  3. package/dist/agents/producer.js +65 -23
  4. package/dist/auth.d.ts +2 -0
  5. package/dist/cli.js +7 -4
  6. package/dist/critic-gate.d.ts +29 -0
  7. package/dist/critic-gate.js +223 -0
  8. package/dist/critic-retrospect.d.ts +64 -0
  9. package/dist/critic-retrospect.js +279 -0
  10. package/dist/critic-taxonomy.d.ts +40 -0
  11. package/dist/critic-taxonomy.js +146 -0
  12. package/dist/growth.d.ts +37 -0
  13. package/dist/growth.js +272 -0
  14. package/dist/integrations/ableton.d.ts +30 -0
  15. package/dist/integrations/ableton.js +66 -0
  16. package/dist/integrations/kbot-control-client.d.ts +66 -0
  17. package/dist/integrations/kbot-control-client.js +224 -0
  18. package/dist/observer.d.ts +13 -0
  19. package/dist/observer.js +5 -1
  20. package/dist/planner/hierarchical/dag.d.ts +71 -0
  21. package/dist/planner/hierarchical/dag.js +97 -0
  22. package/dist/planner/hierarchical/persistence.d.ts +26 -0
  23. package/dist/planner/hierarchical/persistence.js +113 -0
  24. package/dist/planner/hierarchical/session-planner.d.ts +68 -0
  25. package/dist/planner/hierarchical/session-planner.js +141 -0
  26. package/dist/planner/hierarchical/types.d.ts +116 -0
  27. package/dist/planner/hierarchical/types.js +18 -0
  28. package/dist/tool-pipeline.d.ts +39 -1
  29. package/dist/tool-pipeline.js +109 -1
  30. package/dist/tools/ableton-listen.d.ts +2 -0
  31. package/dist/tools/ableton-listen.js +126 -0
  32. package/dist/tools/ableton.js +477 -12
  33. package/dist/tools/index.js +2 -0
  34. package/dist/tools/kbot-control.d.ts +2 -0
  35. package/dist/tools/kbot-control.js +63 -0
  36. package/package.json +1 -1
@@ -57,6 +57,40 @@ export declare function truncationMiddleware(maxSize?: number): ToolMiddleware;
57
57
  * Emits tool_call_start and tool_call_end events.
58
58
  */
59
59
  export declare function telemetryMiddleware(emit: (event: string, data: any) => void): ToolMiddleware;
60
+ /**
61
+ * Thresholds for outcome classification.
62
+ * Exported so downstream code (training, analytics) can mirror them.
63
+ */
64
+ export declare const OUTCOME_EMPTY_THRESHOLD = 5;
65
+ export declare const OUTCOME_LARGE_THRESHOLD = 10240;
66
+ /**
67
+ * Classify a tool execution outcome from its ToolContext.
68
+ *
69
+ * Priority:
70
+ * 1. timeout — aborted with timeout reason
71
+ * 2. error — ctx.error is set (or aborted for any other reason)
72
+ * 3. empty — result present but shorter than OUTCOME_EMPTY_THRESHOLD chars
73
+ * 4. large — result byte length > OUTCOME_LARGE_THRESHOLD
74
+ * 5. success — anything else
75
+ */
76
+ export declare function classifyOutcome(ctx: ToolContext): 'success' | 'error' | 'timeout' | 'empty' | 'large';
77
+ /**
78
+ * Observer middleware — writes an observation to ~/.kbot/observer/session.jsonl.
79
+ *
80
+ * Records the three fields that cannot be backfilled for action-token training:
81
+ * - durationMs: wall-clock time of tool execution
82
+ * - outcome: success | error | timeout | empty | large
83
+ * - resultSize: byte length of the serialized result
84
+ *
85
+ * Emits schema v2 events. Backward compatible — consumers that don't know
86
+ * about the new fields will ignore them (the tokenizer has fallbacks).
87
+ *
88
+ * Place this as the OUTERMOST middleware so duration captures the true
89
+ * wall-clock of the full pipeline (including timeout, truncation, fallback).
90
+ */
91
+ export declare function observerMiddleware(sessionId: string, options?: {
92
+ enabled?: () => boolean;
93
+ }): ToolMiddleware;
60
94
  /**
61
95
  * Execution middleware — the actual tool call.
62
96
  * This should be the last middleware in the pipeline.
@@ -100,7 +134,7 @@ export declare function fallbackMiddleware(rules: FallbackRule[], execute: (name
100
134
  export declare function resourceAwareMiddleware(): ToolMiddleware;
101
135
  /**
102
136
  * Create the default pipeline with the standard middleware stack.
103
- * Order: telemetry? → permission → hooks → resource → metrics → timeout → truncation → fallback? → execution
137
+ * Order: observer? → telemetry? → permission → hooks → resource → metrics → timeout → truncation → fallback? → execution
104
138
  */
105
139
  export declare function createDefaultPipeline(deps: {
106
140
  checkPermission: (name: string, args: any) => Promise<boolean>;
@@ -116,5 +150,9 @@ export declare function createDefaultPipeline(deps: {
116
150
  recordMetrics: (name: string, duration: number, error?: string) => void;
117
151
  emit?: (event: string, data: any) => void;
118
152
  fallbackRules?: FallbackRule[];
153
+ /** If set, observerMiddleware is added as the outermost layer and writes to ~/.kbot/observer/session.jsonl. */
154
+ observerSessionId?: string;
155
+ /** Runtime gate for observer writes. */
156
+ observerEnabled?: () => boolean;
119
157
  }): ToolPipeline;
120
158
  //# sourceMappingURL=tool-pipeline.d.ts.map
@@ -162,6 +162,111 @@ export function telemetryMiddleware(emit) {
162
162
  });
163
163
  };
164
164
  }
165
+ // ── Outcome classification ──
166
+ /**
167
+ * Thresholds for outcome classification.
168
+ * Exported so downstream code (training, analytics) can mirror them.
169
+ */
170
+ export const OUTCOME_EMPTY_THRESHOLD = 5; // result under this many chars → "empty"
171
+ export const OUTCOME_LARGE_THRESHOLD = 10_240; // result over this many bytes → "large"
172
+ /**
173
+ * Classify a tool execution outcome from its ToolContext.
174
+ *
175
+ * Priority:
176
+ * 1. timeout — aborted with timeout reason
177
+ * 2. error — ctx.error is set (or aborted for any other reason)
178
+ * 3. empty — result present but shorter than OUTCOME_EMPTY_THRESHOLD chars
179
+ * 4. large — result byte length > OUTCOME_LARGE_THRESHOLD
180
+ * 5. success — anything else
181
+ */
182
+ export function classifyOutcome(ctx) {
183
+ // Timeout takes precedence over generic error because the error string is set too.
184
+ if (ctx.aborted && /timed?\s*out|timeout/i.test(ctx.abortReason ?? ctx.error ?? '')) {
185
+ return 'timeout';
186
+ }
187
+ if (ctx.error || ctx.aborted)
188
+ return 'error';
189
+ const result = ctx.result ?? '';
190
+ if (result.length < OUTCOME_EMPTY_THRESHOLD)
191
+ return 'empty';
192
+ if (Buffer.byteLength(result, 'utf8') > OUTCOME_LARGE_THRESHOLD)
193
+ return 'large';
194
+ return 'success';
195
+ }
196
+ /**
197
+ * Observer middleware — writes an observation to ~/.kbot/observer/session.jsonl.
198
+ *
199
+ * Records the three fields that cannot be backfilled for action-token training:
200
+ * - durationMs: wall-clock time of tool execution
201
+ * - outcome: success | error | timeout | empty | large
202
+ * - resultSize: byte length of the serialized result
203
+ *
204
+ * Emits schema v2 events. Backward compatible — consumers that don't know
205
+ * about the new fields will ignore them (the tokenizer has fallbacks).
206
+ *
207
+ * Place this as the OUTERMOST middleware so duration captures the true
208
+ * wall-clock of the full pipeline (including timeout, truncation, fallback).
209
+ */
210
+ export function observerMiddleware(sessionId, options = {}) {
211
+ return async (ctx, next) => {
212
+ const start = Date.now();
213
+ let threw = undefined;
214
+ try {
215
+ await next();
216
+ }
217
+ catch (err) {
218
+ // Capture so we can still log; rethrow after.
219
+ threw = err;
220
+ if (!ctx.error)
221
+ ctx.error = err instanceof Error ? err.message : String(err);
222
+ }
223
+ const durationMs = Date.now() - start;
224
+ // Allow runtime opt-out (e.g. user disabled observer).
225
+ if (options.enabled && !options.enabled()) {
226
+ if (threw)
227
+ throw threw;
228
+ return;
229
+ }
230
+ const result = ctx.result ?? '';
231
+ const resultSize = Buffer.byteLength(result, 'utf8');
232
+ const outcome = classifyOutcome(ctx);
233
+ try {
234
+ const { recordObservation } = await import('./observer.js');
235
+ // Extract a few "safe" arg fields for indexing — mirror what agent.ts does.
236
+ const a = (ctx.toolArgs ?? {});
237
+ const args = {};
238
+ if (typeof a.file_path === 'string')
239
+ args.file_path = a.file_path;
240
+ else if (typeof a.path === 'string')
241
+ args.path = a.path;
242
+ if (typeof a.command === 'string')
243
+ args.command = a.command.slice(0, 200);
244
+ if (typeof a.pattern === 'string')
245
+ args.pattern = a.pattern;
246
+ if (typeof a.url === 'string')
247
+ args.url = a.url;
248
+ if (typeof a.query === 'string')
249
+ args.query = a.query;
250
+ recordObservation({
251
+ schema: 2,
252
+ ts: new Date(start).toISOString(),
253
+ tool: ctx.toolName,
254
+ args,
255
+ result_length: result.length,
256
+ session: sessionId,
257
+ error: Boolean(ctx.error),
258
+ durationMs,
259
+ outcome,
260
+ resultSize,
261
+ });
262
+ }
263
+ catch {
264
+ /* observer is non-critical — never break tool execution */
265
+ }
266
+ if (threw)
267
+ throw threw;
268
+ };
269
+ }
165
270
  /**
166
271
  * Execution middleware — the actual tool call.
167
272
  * This should be the last middleware in the pipeline.
@@ -313,10 +418,13 @@ export function resourceAwareMiddleware() {
313
418
  }
314
419
  /**
315
420
  * Create the default pipeline with the standard middleware stack.
316
- * Order: telemetry? → permission → hooks → resource → metrics → timeout → truncation → fallback? → execution
421
+ * Order: observer? → telemetry? → permission → hooks → resource → metrics → timeout → truncation → fallback? → execution
317
422
  */
318
423
  export function createDefaultPipeline(deps) {
319
424
  const pipeline = new ToolPipeline();
425
+ if (deps.observerSessionId) {
426
+ pipeline.use(observerMiddleware(deps.observerSessionId, { enabled: deps.observerEnabled }));
427
+ }
320
428
  if (deps.emit) {
321
429
  pipeline.use(telemetryMiddleware(deps.emit));
322
430
  }
@@ -0,0 +1,2 @@
1
+ export declare function registerAbletonListenTool(): void;
2
+ //# sourceMappingURL=ableton-listen.d.ts.map
@@ -0,0 +1,126 @@
1
+ // ableton_listen — subscribe to real-time LOM events via kbot-control.
2
+ //
3
+ // AbletonOSC has no listener API. kbot-control implements listeners using
4
+ // native Max LiveAPI property callbacks, streamed over the TCP JSON-RPC
5
+ // connection as "notify" messages. This tool exposes that capability to
6
+ // the agent so it can subscribe to beat, playing_position, parameter
7
+ // changes, etc., and pull a buffered history for inspection.
8
+ import { registerTool } from './index.js';
9
+ import { KbotControlClient } from '../integrations/kbot-control-client.js';
10
+ // Per-path ring buffer of recent events so the agent can poll history.
11
+ const MAX_PER_PATH = 100;
12
+ const history = new Map();
13
+ const subscriptions = new Map();
14
+ function recordEvent(path, value) {
15
+ let buf = history.get(path);
16
+ if (!buf) {
17
+ buf = [];
18
+ history.set(path, buf);
19
+ }
20
+ buf.push({ path, value, at: Date.now() });
21
+ if (buf.length > MAX_PER_PATH)
22
+ buf.shift();
23
+ }
24
+ export function registerAbletonListenTool() {
25
+ registerTool({
26
+ name: 'ableton_listen',
27
+ description: 'Subscribe to real-time Ableton events via kbot-control.amxd. ' +
28
+ 'Subscriptions stream over the persistent TCP connection and the tool buffers the last 100 events per path. ' +
29
+ 'Use action="subscribe" with a LOM-ish path (e.g. "song.is_playing", "song.tempo", "tracks[0].output_meter_left"), ' +
30
+ 'then action="history" to pull what came in. action="list" shows active subscriptions. ' +
31
+ 'Requires kbot-control.amxd loaded in Ableton.',
32
+ parameters: {
33
+ action: {
34
+ type: 'string',
35
+ description: '"subscribe" | "unsubscribe" | "list" | "history" | "clear"',
36
+ required: true,
37
+ },
38
+ path: {
39
+ type: 'string',
40
+ description: 'LOM path for subscribe/unsubscribe/history. Examples: "song.is_playing", "song.tempo", "song.current_song_time", "tracks[0].mute".',
41
+ },
42
+ limit: {
43
+ type: 'number',
44
+ description: 'For "history": max events to return (default 25).',
45
+ },
46
+ },
47
+ tier: 'free',
48
+ timeout: 15_000,
49
+ async execute(args) {
50
+ const action = String(args.action || '').toLowerCase();
51
+ const path = args.path !== undefined ? String(args.path) : '';
52
+ try {
53
+ switch (action) {
54
+ case 'subscribe': {
55
+ if (!path)
56
+ return 'Error: subscribe needs a path.';
57
+ if (subscriptions.has(path))
58
+ return `Already subscribed to ${path}`;
59
+ const handler = (value) => recordEvent(path, value);
60
+ await KbotControlClient.get().subscribe(path, handler);
61
+ subscriptions.set(path, handler);
62
+ return `Subscribed to \`${path}\`. Events will be recorded; call action="history" to pull them.`;
63
+ }
64
+ case 'unsubscribe': {
65
+ if (!path)
66
+ return 'Error: unsubscribe needs a path.';
67
+ const handler = subscriptions.get(path);
68
+ if (!handler)
69
+ return `Not subscribed to ${path}`;
70
+ await KbotControlClient.get().unsubscribe(path, handler);
71
+ subscriptions.delete(path);
72
+ return `Unsubscribed from ${path}`;
73
+ }
74
+ case 'list': {
75
+ if (subscriptions.size === 0)
76
+ return 'No active subscriptions.';
77
+ const lines = ['## Active subscriptions', ''];
78
+ for (const p of subscriptions.keys()) {
79
+ const buf = history.get(p);
80
+ lines.push(`- \`${p}\` — ${buf ? buf.length : 0} events buffered`);
81
+ }
82
+ return lines.join('\n');
83
+ }
84
+ case 'history': {
85
+ if (!path) {
86
+ // Return summary of all paths
87
+ if (history.size === 0)
88
+ return 'No events recorded yet.';
89
+ const lines = ['## Event history summary', ''];
90
+ for (const [p, buf] of history.entries()) {
91
+ const last = buf[buf.length - 1];
92
+ lines.push(`- \`${p}\`: ${buf.length} events, last value = ${JSON.stringify(last?.value)}`);
93
+ }
94
+ return lines.join('\n');
95
+ }
96
+ const buf = history.get(path);
97
+ if (!buf || buf.length === 0)
98
+ return `No events recorded for \`${path}\`.`;
99
+ const limit = Number(args.limit) || 25;
100
+ const recent = buf.slice(-limit);
101
+ const lines = [`## \`${path}\` — last ${recent.length} events`, ''];
102
+ for (const ev of recent) {
103
+ const dt = new Date(ev.at).toISOString().slice(11, 23);
104
+ lines.push(`- ${dt} → ${JSON.stringify(ev.value)}`);
105
+ }
106
+ return lines.join('\n');
107
+ }
108
+ case 'clear': {
109
+ if (path) {
110
+ history.delete(path);
111
+ return `Cleared history for ${path}`;
112
+ }
113
+ history.clear();
114
+ return 'Cleared all history';
115
+ }
116
+ default:
117
+ return `Unknown action "${action}". Options: subscribe, unsubscribe, list, history, clear`;
118
+ }
119
+ }
120
+ catch (e) {
121
+ return `ableton_listen error: ${e.message}`;
122
+ }
123
+ },
124
+ });
125
+ }
126
+ //# sourceMappingURL=ableton-listen.js.map