@dot-ai/adapter-openclaw 0.5.2 → 0.7.0
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 +205 -0
- package/dist/__tests__/plugin-integration.test.d.ts +2 -0
- package/dist/__tests__/plugin-integration.test.d.ts.map +1 -0
- package/dist/__tests__/plugin-integration.test.js +164 -0
- package/dist/__tests__/plugin-integration.test.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +48 -72
- package/dist/index.js.map +1 -1
- package/openclaw.plugin.json +9 -0
- package/package.json +2 -2
- package/src/__tests__/plugin-integration.test.ts +233 -0
- package/src/index.ts +66 -89
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { DotAiRuntime, EventBus, ADAPTER_CAPABILITIES } from '@dot-ai/core';
|
|
3
|
+
|
|
4
|
+
// ── Mock OpenClaw Plugin API ──
|
|
5
|
+
|
|
6
|
+
interface CapturedHook {
|
|
7
|
+
event: string;
|
|
8
|
+
handler: (...args: unknown[]) => unknown;
|
|
9
|
+
options?: { priority?: number };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface CapturedService {
|
|
13
|
+
id: string;
|
|
14
|
+
start: (ctx: { logger: { info: (msg: string) => void } }) => void;
|
|
15
|
+
stop: (ctx: { logger: { info: (msg: string) => void } }) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface CapturedTool {
|
|
19
|
+
factory?: (ctx: Record<string, unknown>) => unknown;
|
|
20
|
+
opts?: { name?: string; names?: string[] };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function createMockOpenClawApi() {
|
|
24
|
+
const logs: string[] = [];
|
|
25
|
+
const hooks: CapturedHook[] = [];
|
|
26
|
+
const services: CapturedService[] = [];
|
|
27
|
+
const tools: CapturedTool[] = [];
|
|
28
|
+
|
|
29
|
+
const api = {
|
|
30
|
+
logger: {
|
|
31
|
+
info: (msg: string) => logs.push(msg),
|
|
32
|
+
debug: (msg: string) => logs.push(`[debug] ${msg}`),
|
|
33
|
+
},
|
|
34
|
+
pluginConfig: undefined as Record<string, unknown> | undefined,
|
|
35
|
+
on(
|
|
36
|
+
event: string,
|
|
37
|
+
handler: (...args: unknown[]) => unknown,
|
|
38
|
+
options?: { priority?: number },
|
|
39
|
+
) {
|
|
40
|
+
hooks.push({ event, handler, options });
|
|
41
|
+
},
|
|
42
|
+
registerService(service: CapturedService) {
|
|
43
|
+
services.push(service);
|
|
44
|
+
},
|
|
45
|
+
registerTool(factory: (ctx: Record<string, unknown>) => unknown, opts?: { name?: string; names?: string[] }) {
|
|
46
|
+
tools.push({ factory, opts });
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return { api, logs, hooks, services, tools };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ── Tests ──
|
|
54
|
+
|
|
55
|
+
describe('OpenClaw Plugin Integration', () => {
|
|
56
|
+
describe('plugin.register() structure', () => {
|
|
57
|
+
it('registers before_agent_start hook with priority 10', async () => {
|
|
58
|
+
const { api, hooks } = createMockOpenClawApi();
|
|
59
|
+
|
|
60
|
+
const { default: plugin } = await import('../index.js');
|
|
61
|
+
plugin.register(api as never);
|
|
62
|
+
|
|
63
|
+
const beforeStart = hooks.find(h => h.event === 'before_agent_start');
|
|
64
|
+
expect(beforeStart).toBeDefined();
|
|
65
|
+
expect(beforeStart!.options?.priority).toBe(10);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('registers after_agent_end hook', async () => {
|
|
69
|
+
const { api, hooks } = createMockOpenClawApi();
|
|
70
|
+
const { default: plugin } = await import('../index.js');
|
|
71
|
+
plugin.register(api as never);
|
|
72
|
+
|
|
73
|
+
const afterEnd = hooks.find(h => h.event === 'after_agent_end');
|
|
74
|
+
expect(afterEnd).toBeDefined();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('registers dot-ai service', async () => {
|
|
78
|
+
const { api, services } = createMockOpenClawApi();
|
|
79
|
+
const { default: plugin } = await import('../index.js');
|
|
80
|
+
plugin.register(api as never);
|
|
81
|
+
|
|
82
|
+
const svc = services.find(s => s.id === 'dot-ai');
|
|
83
|
+
expect(svc).toBeDefined();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('registers tool factory for capabilities', async () => {
|
|
87
|
+
const { api, tools } = createMockOpenClawApi();
|
|
88
|
+
const { default: plugin } = await import('../index.js');
|
|
89
|
+
plugin.register(api as never);
|
|
90
|
+
|
|
91
|
+
expect(tools.length).toBe(1);
|
|
92
|
+
expect(tools[0].opts?.names).toContain('memory_recall');
|
|
93
|
+
expect(tools[0].opts?.names).toContain('memory_store');
|
|
94
|
+
expect(tools[0].opts?.names).toContain('task_list');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('logs plugin version v6', async () => {
|
|
98
|
+
const { api, logs } = createMockOpenClawApi();
|
|
99
|
+
const { default: plugin } = await import('../index.js');
|
|
100
|
+
plugin.register(api as never);
|
|
101
|
+
|
|
102
|
+
expect(logs.some(l => l.includes('v6'))).toBe(true);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('has correct plugin metadata', async () => {
|
|
106
|
+
const { default: plugin } = await import('../index.js');
|
|
107
|
+
expect(plugin.id).toBe('dot-ai');
|
|
108
|
+
expect(plugin.version).toBe('6.0.0');
|
|
109
|
+
expect(plugin.kind).toBe('memory');
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('before_agent_start → boot → processPrompt', () => {
|
|
114
|
+
it('skips sub-agent sessions', async () => {
|
|
115
|
+
const { api, hooks, logs } = createMockOpenClawApi();
|
|
116
|
+
const { default: plugin } = await import('../index.js');
|
|
117
|
+
plugin.register(api as never);
|
|
118
|
+
|
|
119
|
+
const beforeStart = hooks.find(h => h.event === 'before_agent_start')!;
|
|
120
|
+
const result = await beforeStart.handler(
|
|
121
|
+
{},
|
|
122
|
+
{ workspaceDir: '/tmp/test', sessionKey: 'session:subagent:1', prompt: 'hello' },
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
expect(result).toBeUndefined();
|
|
126
|
+
expect(logs.some(l => l.includes('Sub-agent'))).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('skips cron sessions', async () => {
|
|
130
|
+
const { api, hooks, logs } = createMockOpenClawApi();
|
|
131
|
+
const { default: plugin } = await import('../index.js');
|
|
132
|
+
plugin.register(api as never);
|
|
133
|
+
|
|
134
|
+
const beforeStart = hooks.find(h => h.event === 'before_agent_start')!;
|
|
135
|
+
const result = await beforeStart.handler(
|
|
136
|
+
{},
|
|
137
|
+
{ workspaceDir: '/tmp/test', sessionKey: 'session:cron:cleanup', prompt: 'cleanup' },
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
expect(result).toBeUndefined();
|
|
141
|
+
expect(logs.some(l => l.includes('Sub-agent') || l.includes('cron'))).toBe(true);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('skips when no workspaceDir', async () => {
|
|
145
|
+
const { api, hooks, logs } = createMockOpenClawApi();
|
|
146
|
+
const { default: plugin } = await import('../index.js');
|
|
147
|
+
plugin.register(api as never);
|
|
148
|
+
|
|
149
|
+
const beforeStart = hooks.find(h => h.event === 'before_agent_start')!;
|
|
150
|
+
const result = await beforeStart.handler(
|
|
151
|
+
{},
|
|
152
|
+
{ prompt: 'hello' },
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
expect(result).toBeUndefined();
|
|
156
|
+
expect(logs.some(l => l.includes('No workspaceDir'))).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe('DotAiRuntime lifecycle (v6 extension-only)', () => {
|
|
161
|
+
it('boots and processes prompt', async () => {
|
|
162
|
+
const runtime = new DotAiRuntime({
|
|
163
|
+
workspaceRoot: '/tmp/nonexistent',
|
|
164
|
+
skipIdentities: true,
|
|
165
|
+
});
|
|
166
|
+
await runtime.boot();
|
|
167
|
+
expect(runtime.isBooted).toBe(true);
|
|
168
|
+
|
|
169
|
+
const { formatted } = await runtime.processPrompt('hello world');
|
|
170
|
+
expect(formatted).toBeDefined();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('diagnostics show extension info', async () => {
|
|
174
|
+
const runtime = new DotAiRuntime({
|
|
175
|
+
workspaceRoot: '/tmp/nonexistent',
|
|
176
|
+
});
|
|
177
|
+
await runtime.boot();
|
|
178
|
+
|
|
179
|
+
const diag = runtime.diagnostics;
|
|
180
|
+
expect(diag.extensions).toEqual([]);
|
|
181
|
+
expect(diag.capabilityCount).toBe(0);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('learn fires agent_end without throwing', async () => {
|
|
185
|
+
const runtime = new DotAiRuntime({
|
|
186
|
+
workspaceRoot: '/tmp/nonexistent',
|
|
187
|
+
});
|
|
188
|
+
await runtime.boot();
|
|
189
|
+
await runtime.learn('test response');
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe('OpenClaw adapter capability matrix', () => {
|
|
194
|
+
it('OpenClaw supports context_inject, agent_end, session_start', () => {
|
|
195
|
+
const supported = ADAPTER_CAPABILITIES['openclaw'];
|
|
196
|
+
expect(supported.has('context_inject')).toBe(true);
|
|
197
|
+
expect(supported.has('agent_end')).toBe(true);
|
|
198
|
+
expect(supported.has('session_start')).toBe(true);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('OpenClaw does NOT support tool_call, tool_result, context_modify', () => {
|
|
202
|
+
const supported = ADAPTER_CAPABILITIES['openclaw'];
|
|
203
|
+
expect(supported.has('tool_call')).toBe(false);
|
|
204
|
+
expect(supported.has('tool_result')).toBe(false);
|
|
205
|
+
expect(supported.has('context_modify')).toBe(false);
|
|
206
|
+
expect(supported.has('turn_start')).toBe(false);
|
|
207
|
+
expect(supported.has('turn_end')).toBe(false);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe('EventBus inter-extension communication', () => {
|
|
212
|
+
it('extensions can communicate via EventBus', async () => {
|
|
213
|
+
const eventBus = new EventBus();
|
|
214
|
+
const received: unknown[] = [];
|
|
215
|
+
|
|
216
|
+
eventBus.on('custom:auth-fix', (data: unknown) => {
|
|
217
|
+
received.push(data);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
eventBus.emit('custom:auth-fix', { file: 'auth.ts', action: 'refactored' });
|
|
221
|
+
|
|
222
|
+
expect(received).toHaveLength(1);
|
|
223
|
+
expect(received[0]).toEqual({ file: 'auth.ts', action: 'refactored' });
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('EventBus errors dont propagate', () => {
|
|
227
|
+
const eventBus = new EventBus();
|
|
228
|
+
eventBus.on('test', () => { throw new Error('boom'); });
|
|
229
|
+
|
|
230
|
+
expect(() => eventBus.emit('test')).not.toThrow();
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -1,23 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* dot-ai OpenClaw plugin
|
|
3
|
-
*
|
|
4
|
-
* Hooks into before_agent_start to run the full dot-ai pipeline:
|
|
5
|
-
* loadConfig → registerDefaults → createProviders → boot → enrich → formatContext
|
|
2
|
+
* dot-ai OpenClaw plugin v6
|
|
6
3
|
*
|
|
4
|
+
* Hooks into before_agent_start to run the full dot-ai pipeline via DotAiRuntime.
|
|
5
|
+
* Uses the v6 extension-based pipeline — no providers required.
|
|
7
6
|
* Returns enriched context as prependContext for the agent.
|
|
8
7
|
*/
|
|
9
|
-
import {
|
|
10
|
-
loadConfig,
|
|
11
|
-
registerDefaults,
|
|
12
|
-
registerProvider,
|
|
13
|
-
createProviders,
|
|
14
|
-
boot,
|
|
15
|
-
enrich,
|
|
16
|
-
injectRoot,
|
|
17
|
-
formatContext,
|
|
18
|
-
type Providers,
|
|
19
|
-
type BootCache,
|
|
20
|
-
} from '@dot-ai/core';
|
|
8
|
+
import { DotAiRuntime } from '@dot-ai/core';
|
|
21
9
|
|
|
22
10
|
// Inline OpenClaw plugin API types
|
|
23
11
|
interface OpenClawLogger {
|
|
@@ -41,67 +29,59 @@ interface OpenClawPluginApi {
|
|
|
41
29
|
start(ctx: { logger: OpenClawLogger }): void;
|
|
42
30
|
stop(ctx: { logger: OpenClawLogger }): void;
|
|
43
31
|
}): void;
|
|
32
|
+
registerTool(tool: OpenClawTool | OpenClawToolFactory, opts?: { name?: string; names?: string[] }): void;
|
|
44
33
|
}
|
|
45
34
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
* plugins.entries.dot-ai.config.customProviders: [
|
|
50
|
-
* { type: "cockpit", module: "/abs/path/to/provider.ts" }
|
|
51
|
-
* ]
|
|
52
|
-
*/
|
|
53
|
-
async function loadCustomProviders(
|
|
54
|
-
config: Record<string, unknown>,
|
|
55
|
-
logger: OpenClawLogger,
|
|
56
|
-
): Promise<void> {
|
|
57
|
-
const providers = config.customProviders;
|
|
58
|
-
if (!Array.isArray(providers)) return;
|
|
59
|
-
|
|
60
|
-
for (const entry of providers) {
|
|
61
|
-
if (!entry || typeof entry !== 'object' || !('type' in entry) || !('module' in entry)) continue;
|
|
62
|
-
|
|
63
|
-
const { type, module: modulePath } = entry as { type: string; module: string };
|
|
64
|
-
try {
|
|
65
|
-
const mod = await import(modulePath);
|
|
66
|
-
// Find the exported provider class or factory
|
|
67
|
-
for (const [name, exported] of Object.entries(mod)) {
|
|
68
|
-
if (typeof exported === 'function') {
|
|
69
|
-
if (name.endsWith('Provider')) {
|
|
70
|
-
const ProviderClass = exported as new (opts: Record<string, unknown>) => unknown;
|
|
71
|
-
registerProvider(`@custom/${type}`, (opts) => new ProviderClass(opts));
|
|
72
|
-
logger.info(`[dot-ai] Registered custom provider: ${type} (${name})`);
|
|
73
|
-
break;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
} catch (err) {
|
|
78
|
-
logger.info(`[dot-ai] Failed to load custom provider "${type}" from ${modulePath}: ${err}`);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
35
|
+
interface OpenClawToolResult {
|
|
36
|
+
content: Array<{ type: string; text: string }>;
|
|
37
|
+
details?: Record<string, unknown>;
|
|
81
38
|
}
|
|
82
39
|
|
|
40
|
+
interface OpenClawTool {
|
|
41
|
+
name: string;
|
|
42
|
+
label: string;
|
|
43
|
+
description: string;
|
|
44
|
+
parameters: Record<string, unknown>;
|
|
45
|
+
execute(toolCallId: string, params: Record<string, unknown>): Promise<OpenClawToolResult>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
type OpenClawToolFactory = (ctx: Record<string, unknown>) => OpenClawTool | OpenClawTool[] | null;
|
|
49
|
+
|
|
83
50
|
// Session-level cache
|
|
84
|
-
let
|
|
85
|
-
let cachedBoot: BootCache | null = null;
|
|
51
|
+
let cachedRuntime: DotAiRuntime | null = null;
|
|
86
52
|
let cachedWorkspace: string | null = null;
|
|
87
53
|
|
|
88
54
|
const plugin = {
|
|
89
55
|
id: 'dot-ai',
|
|
90
56
|
name: 'dot-ai — Universal AI Workspace Convention',
|
|
91
|
-
version: '0.
|
|
57
|
+
version: '6.0.0',
|
|
92
58
|
description: 'Deterministic context enrichment for OpenClaw agents',
|
|
59
|
+
kind: 'memory' as const,
|
|
93
60
|
|
|
94
61
|
register(api: OpenClawPluginApi) {
|
|
95
|
-
api.logger.info('[dot-ai] Plugin loaded (
|
|
96
|
-
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
62
|
+
api.logger.info('[dot-ai] Plugin loaded (v6)');
|
|
63
|
+
|
|
64
|
+
// Register tools from core capabilities (delegates to extensions)
|
|
65
|
+
api.registerTool(
|
|
66
|
+
(_ctx: Record<string, unknown>) => {
|
|
67
|
+
if (!cachedRuntime?.isBooted) return null;
|
|
68
|
+
const capabilities = cachedRuntime.capabilities;
|
|
69
|
+
return capabilities.map((cap): OpenClawTool => ({
|
|
70
|
+
name: cap.name,
|
|
71
|
+
label: cap.name.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase()),
|
|
72
|
+
description: cap.description,
|
|
73
|
+
parameters: cap.parameters,
|
|
74
|
+
async execute(_toolCallId: string, params: Record<string, unknown>): Promise<OpenClawToolResult> {
|
|
75
|
+
const result = await cap.execute(params);
|
|
76
|
+
return {
|
|
77
|
+
content: [{ type: 'text', text: result.text }],
|
|
78
|
+
...(result.details && { details: result.details }),
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
}));
|
|
82
|
+
},
|
|
83
|
+
{ names: ['memory_recall', 'memory_store', 'task_list', 'task_create', 'task_update'] },
|
|
84
|
+
);
|
|
105
85
|
|
|
106
86
|
// Hook: before_agent_start — run the full pipeline
|
|
107
87
|
api.on(
|
|
@@ -110,16 +90,12 @@ const plugin = {
|
|
|
110
90
|
_event: unknown,
|
|
111
91
|
ctx: { workspaceDir?: string; sessionKey?: string; prompt?: string },
|
|
112
92
|
) => {
|
|
113
|
-
// Ensure custom providers are registered before proceeding
|
|
114
|
-
await providerPromise;
|
|
115
|
-
|
|
116
93
|
const workspaceDir = ctx.workspaceDir;
|
|
117
94
|
if (!workspaceDir) {
|
|
118
95
|
api.logger.info('[dot-ai] No workspaceDir, skipping');
|
|
119
96
|
return;
|
|
120
97
|
}
|
|
121
98
|
|
|
122
|
-
// Skip sub-agent/cron sessions
|
|
123
99
|
const isSubagent = ctx.sessionKey?.includes(':subagent:') || ctx.sessionKey?.includes(':cron:');
|
|
124
100
|
if (isSubagent) {
|
|
125
101
|
api.logger.debug?.('[dot-ai] Sub-agent/cron session, skipping');
|
|
@@ -127,33 +103,25 @@ const plugin = {
|
|
|
127
103
|
}
|
|
128
104
|
|
|
129
105
|
try {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
// Inject workspaceDir into all provider options
|
|
135
|
-
const config = injectRoot(rawConfig, workspaceDir);
|
|
136
|
-
cachedProviders = await createProviders(config);
|
|
137
|
-
cachedBoot = await boot(cachedProviders);
|
|
106
|
+
if (!cachedRuntime || cachedWorkspace !== workspaceDir) {
|
|
107
|
+
cachedRuntime = new DotAiRuntime({ workspaceRoot: workspaceDir });
|
|
108
|
+
await cachedRuntime.boot();
|
|
138
109
|
cachedWorkspace = workspaceDir;
|
|
139
|
-
api.logger.info(
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Enrich the prompt
|
|
143
|
-
const prompt = ctx.prompt ?? '';
|
|
144
|
-
const enriched = await enrich(prompt, cachedProviders, cachedBoot!);
|
|
110
|
+
api.logger.info('[dot-ai] Runtime booted (v6)');
|
|
145
111
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
112
|
+
// Log extension diagnostics
|
|
113
|
+
const diag = cachedRuntime.diagnostics;
|
|
114
|
+
api.logger.info(`[dot-ai] extensions=${diag.extensions.length}, capabilities=${diag.capabilityCount}`);
|
|
115
|
+
if (diag.vocabularySize !== undefined) {
|
|
116
|
+
api.logger.info(`[dot-ai] Vocabulary size: ${diag.vocabularySize}`);
|
|
150
117
|
}
|
|
151
118
|
}
|
|
152
119
|
|
|
153
|
-
|
|
154
|
-
const formatted =
|
|
120
|
+
const prompt = ctx.prompt ?? '';
|
|
121
|
+
const { formatted, enriched } = await cachedRuntime.processPrompt(prompt);
|
|
122
|
+
|
|
155
123
|
if (formatted) {
|
|
156
|
-
api.logger.info(`[dot-ai] Injected: ${enriched.identities.length}
|
|
124
|
+
api.logger.info(`[dot-ai] Injected: ${enriched.identities.length} ids, ${enriched.memories.length} mems, ${enriched.skills.length} skills`);
|
|
157
125
|
return { prependContext: formatted };
|
|
158
126
|
}
|
|
159
127
|
} catch (err) {
|
|
@@ -164,6 +132,15 @@ const plugin = {
|
|
|
164
132
|
{ priority: 10 },
|
|
165
133
|
);
|
|
166
134
|
|
|
135
|
+
// Hook: after_agent_end — feed response back to runtime for learning + extension events
|
|
136
|
+
api.on('after_agent_end', async (_event, ctx) => {
|
|
137
|
+
if (!cachedRuntime) return;
|
|
138
|
+
const response = (ctx as { response?: string }).response ?? '';
|
|
139
|
+
if (response) {
|
|
140
|
+
await cachedRuntime.learn(response);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
167
144
|
// Service registration
|
|
168
145
|
api.registerService({
|
|
169
146
|
id: 'dot-ai',
|