@kernel.chat/kbot 3.99.20 → 3.99.21
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 +11 -0
- package/dist/agent.js +23 -0
- package/dist/agents/producer.js +65 -23
- package/dist/auth.d.ts +2 -0
- package/dist/cli.js +7 -4
- package/dist/critic-gate.d.ts +26 -0
- package/dist/critic-gate.js +220 -0
- package/dist/critic-retrospect.d.ts +64 -0
- package/dist/critic-retrospect.js +279 -0
- package/dist/growth.d.ts +37 -0
- package/dist/growth.js +272 -0
- package/dist/integrations/ableton.d.ts +30 -0
- package/dist/integrations/ableton.js +66 -0
- package/dist/integrations/kbot-control-client.d.ts +66 -0
- package/dist/integrations/kbot-control-client.js +224 -0
- package/dist/observer.d.ts +13 -0
- package/dist/observer.js +5 -1
- package/dist/planner/hierarchical/persistence.d.ts +26 -0
- package/dist/planner/hierarchical/persistence.js +113 -0
- package/dist/planner/hierarchical/session-planner.d.ts +68 -0
- package/dist/planner/hierarchical/session-planner.js +141 -0
- package/dist/planner/hierarchical/types.d.ts +116 -0
- package/dist/planner/hierarchical/types.js +18 -0
- package/dist/tool-pipeline.d.ts +39 -1
- package/dist/tool-pipeline.js +109 -1
- package/dist/tools/ableton-listen.d.ts +2 -0
- package/dist/tools/ableton-listen.js +126 -0
- package/dist/tools/ableton.js +477 -12
- package/dist/tools/index.js +2 -0
- package/dist/tools/kbot-control.d.ts +2 -0
- package/dist/tools/kbot-control.js +63 -0
- package/package.json +1 -1
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hierarchical Planner — Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* Four tiers, increasing temporal resolution:
|
|
5
|
+
* Tier 1 SessionGoal days → weeks (Opus, rarely re-planned)
|
|
6
|
+
* Tier 2 Phase hours (Opus, on scope shift)
|
|
7
|
+
* Tier 3 Action minutes (Sonnet, per user turn)
|
|
8
|
+
* Tier 4 ToolCallSpec seconds (Haiku, per tool call)
|
|
9
|
+
*
|
|
10
|
+
* Inspired by Suno's 3-stage transformer (semantic → coarse acoustic → fine
|
|
11
|
+
* acoustic). Coarse intent is stable; fine actuation is cheap and rewritten.
|
|
12
|
+
*
|
|
13
|
+
* See DESIGN.md in this directory for the full rationale, cost model,
|
|
14
|
+
* decision logic, and integration plan. This file is types-only — no imports
|
|
15
|
+
* from heavy runtime modules, no implementation.
|
|
16
|
+
*/
|
|
17
|
+
import type { PlanStep } from '../../planner.js';
|
|
18
|
+
/** Long-lived user objective that spans many turns and possibly many sessions. */
|
|
19
|
+
export interface SessionGoal {
|
|
20
|
+
/** Stable identifier (uuid). Persists across sessions. */
|
|
21
|
+
id: string;
|
|
22
|
+
/** Short title: "ship hierarchical planner v1". */
|
|
23
|
+
title: string;
|
|
24
|
+
/** 1–3 sentence rationale: what success looks like. */
|
|
25
|
+
intent: string;
|
|
26
|
+
/** Acceptance criteria — bullet list the user would agree closes the goal. */
|
|
27
|
+
acceptance: string[];
|
|
28
|
+
/** ISO timestamps. */
|
|
29
|
+
createdAt: string;
|
|
30
|
+
updatedAt: string;
|
|
31
|
+
/** Lifecycle. `paused` goals stay on disk but don't get re-planned. */
|
|
32
|
+
status: 'active' | 'paused' | 'completed' | 'abandoned';
|
|
33
|
+
/** Free-form tags: repo name, domain, user-supplied label. */
|
|
34
|
+
tags?: string[];
|
|
35
|
+
}
|
|
36
|
+
/** Coarse mode the agent is currently operating in. */
|
|
37
|
+
export type PhaseKind = 'explore' | 'build' | 'debug' | 'review' | 'write' | 'refactor' | 'deploy' | 'other';
|
|
38
|
+
/** A contiguous stretch of work under one mode, toward one milestone. */
|
|
39
|
+
export interface Phase {
|
|
40
|
+
id: string;
|
|
41
|
+
/** Parent goal. */
|
|
42
|
+
goalId: string;
|
|
43
|
+
kind: PhaseKind;
|
|
44
|
+
/** What this phase commits to producing. */
|
|
45
|
+
objective: string;
|
|
46
|
+
/** Exit criteria — when `kind` should flip or phase should close. */
|
|
47
|
+
exitCriteria: string[];
|
|
48
|
+
/** Files or subsystems scoped in. */
|
|
49
|
+
scope?: string[];
|
|
50
|
+
/** ISO timestamps. */
|
|
51
|
+
startedAt: string;
|
|
52
|
+
endedAt?: string;
|
|
53
|
+
status: 'active' | 'done' | 'aborted';
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* One action = one user turn's plan. Steps are the existing `PlanStep`s so we
|
|
57
|
+
* stay compatible with `planner.ts#executePlan`.
|
|
58
|
+
*/
|
|
59
|
+
export interface ActionStep extends PlanStep {
|
|
60
|
+
}
|
|
61
|
+
export interface Action {
|
|
62
|
+
id: string;
|
|
63
|
+
phaseId: string;
|
|
64
|
+
/** Verbatim user turn that triggered this action. */
|
|
65
|
+
userTurn: string;
|
|
66
|
+
/** One-sentence plan. */
|
|
67
|
+
summary: string;
|
|
68
|
+
/** Ordered steps; each step is a PlanStep-compatible record. */
|
|
69
|
+
steps: ActionStep[];
|
|
70
|
+
/** Agents this action expects to consult (from learned-router). */
|
|
71
|
+
expectedAgents?: string[];
|
|
72
|
+
createdAt: string;
|
|
73
|
+
status: 'pending' | 'running' | 'done' | 'failed';
|
|
74
|
+
}
|
|
75
|
+
/** Coarse hazard class used to gate permissions and verdict logic. */
|
|
76
|
+
export type SideEffectClass = 'pure' | 'read' | 'write' | 'exec' | 'network' | 'destructive' | 'external';
|
|
77
|
+
/** The final low-level tool call. Haiku fills this in. */
|
|
78
|
+
export interface ToolCallSpec {
|
|
79
|
+
id: string;
|
|
80
|
+
actionId: string;
|
|
81
|
+
stepId: number;
|
|
82
|
+
tool: string;
|
|
83
|
+
args: Record<string, unknown>;
|
|
84
|
+
sideEffect: SideEffectClass;
|
|
85
|
+
/** Optional prediction of what the tool should return on success. */
|
|
86
|
+
expectedOutcome?: string;
|
|
87
|
+
/** Hard ceiling in ms; falls back to pipeline default when absent. */
|
|
88
|
+
timeoutMs?: number;
|
|
89
|
+
}
|
|
90
|
+
export type VerdictDecision = 'continue' | 'revise-action' | 'revise-phase' | 'revise-goal' | 'abort';
|
|
91
|
+
/** Emitted after every tool call; consumed by the up-delegation ladder. */
|
|
92
|
+
export interface TierVerdict {
|
|
93
|
+
decision: VerdictDecision;
|
|
94
|
+
tier: 'tool' | 'action' | 'phase' | 'goal';
|
|
95
|
+
reason: string;
|
|
96
|
+
/** Evidence: tool error, failed assertion, diff summary. */
|
|
97
|
+
evidence?: string;
|
|
98
|
+
}
|
|
99
|
+
export interface TurnMetrics {
|
|
100
|
+
tier1Calls: number;
|
|
101
|
+
tier2Calls: number;
|
|
102
|
+
tier3Calls: number;
|
|
103
|
+
tier4Calls: number;
|
|
104
|
+
tokensIn: number;
|
|
105
|
+
tokensOut: number;
|
|
106
|
+
wallMs: number;
|
|
107
|
+
}
|
|
108
|
+
/** Top-level return of `HierarchicalPlanner.planTurn`. */
|
|
109
|
+
export interface PlannerResult {
|
|
110
|
+
goal: SessionGoal;
|
|
111
|
+
phase: Phase;
|
|
112
|
+
action: Action;
|
|
113
|
+
verdicts: TierVerdict[];
|
|
114
|
+
metrics: TurnMetrics;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hierarchical Planner — Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* Four tiers, increasing temporal resolution:
|
|
5
|
+
* Tier 1 SessionGoal days → weeks (Opus, rarely re-planned)
|
|
6
|
+
* Tier 2 Phase hours (Opus, on scope shift)
|
|
7
|
+
* Tier 3 Action minutes (Sonnet, per user turn)
|
|
8
|
+
* Tier 4 ToolCallSpec seconds (Haiku, per tool call)
|
|
9
|
+
*
|
|
10
|
+
* Inspired by Suno's 3-stage transformer (semantic → coarse acoustic → fine
|
|
11
|
+
* acoustic). Coarse intent is stable; fine actuation is cheap and rewritten.
|
|
12
|
+
*
|
|
13
|
+
* See DESIGN.md in this directory for the full rationale, cost model,
|
|
14
|
+
* decision logic, and integration plan. This file is types-only — no imports
|
|
15
|
+
* from heavy runtime modules, no implementation.
|
|
16
|
+
*/
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=types.js.map
|
package/dist/tool-pipeline.d.ts
CHANGED
|
@@ -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
|
package/dist/tool-pipeline.js
CHANGED
|
@@ -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,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
|