@dot-ai/adapter-openclaw 0.11.6 → 0.11.7
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/dist/__tests__/plugin-integration.test.js +54 -17
- package/dist/__tests__/plugin-integration.test.js.map +1 -1
- package/dist/index.d.ts +14 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +116 -50
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/plugin-integration.test.ts +74 -22
- package/src/index.ts +176 -60
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -13,6 +13,12 @@ interface CapturedHook {
|
|
|
13
13
|
options?: { priority?: number };
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
interface CapturedInternalHook {
|
|
17
|
+
events: string | string[];
|
|
18
|
+
handler: (...args: unknown[]) => unknown;
|
|
19
|
+
opts?: { name?: string; description?: string };
|
|
20
|
+
}
|
|
21
|
+
|
|
16
22
|
interface CapturedService {
|
|
17
23
|
id: string;
|
|
18
24
|
start: (ctx: { logger: { info: (msg: string) => void } }) => void;
|
|
@@ -27,6 +33,7 @@ interface CapturedTool {
|
|
|
27
33
|
function createMockOpenClawApi() {
|
|
28
34
|
const logs: string[] = [];
|
|
29
35
|
const hooks: CapturedHook[] = [];
|
|
36
|
+
const internalHooks: CapturedInternalHook[] = [];
|
|
30
37
|
const services: CapturedService[] = [];
|
|
31
38
|
const tools: CapturedTool[] = [];
|
|
32
39
|
|
|
@@ -43,6 +50,13 @@ function createMockOpenClawApi() {
|
|
|
43
50
|
) {
|
|
44
51
|
hooks.push({ event, handler, options });
|
|
45
52
|
},
|
|
53
|
+
registerHook(
|
|
54
|
+
events: string | string[],
|
|
55
|
+
handler: (...args: unknown[]) => unknown,
|
|
56
|
+
opts?: { name?: string; description?: string },
|
|
57
|
+
) {
|
|
58
|
+
internalHooks.push({ events, handler, opts });
|
|
59
|
+
},
|
|
46
60
|
registerService(service: CapturedService) {
|
|
47
61
|
services.push(service);
|
|
48
62
|
},
|
|
@@ -51,31 +65,70 @@ function createMockOpenClawApi() {
|
|
|
51
65
|
},
|
|
52
66
|
};
|
|
53
67
|
|
|
54
|
-
return { api, logs, hooks, services, tools };
|
|
68
|
+
return { api, logs, hooks, internalHooks, services, tools };
|
|
55
69
|
}
|
|
56
70
|
|
|
57
71
|
// ── Tests ──
|
|
58
72
|
|
|
59
73
|
describe('OpenClaw Plugin Integration', () => {
|
|
60
74
|
describe('plugin.register() structure', () => {
|
|
61
|
-
it('registers
|
|
75
|
+
it('registers before_prompt_build hook with priority 10', async () => {
|
|
62
76
|
const { api, hooks } = createMockOpenClawApi();
|
|
63
77
|
|
|
64
78
|
const { default: plugin } = await import('../index.js');
|
|
65
79
|
plugin.register(api as never);
|
|
66
80
|
|
|
67
|
-
const
|
|
68
|
-
expect(
|
|
69
|
-
expect(
|
|
81
|
+
const beforeBuild = hooks.find(h => h.event === 'before_prompt_build');
|
|
82
|
+
expect(beforeBuild).toBeDefined();
|
|
83
|
+
expect(beforeBuild!.options?.priority).toBe(10);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('registers agent:bootstrap internal hook', async () => {
|
|
87
|
+
const { api, internalHooks } = createMockOpenClawApi();
|
|
88
|
+
const { default: plugin } = await import('../index.js');
|
|
89
|
+
plugin.register(api as never);
|
|
90
|
+
|
|
91
|
+
const bootstrapHook = internalHooks.find(h => h.events === 'agent:bootstrap');
|
|
92
|
+
expect(bootstrapHook).toBeDefined();
|
|
93
|
+
expect(bootstrapHook!.opts?.name).toBe('dot-ai-bootstrap-filter');
|
|
70
94
|
});
|
|
71
95
|
|
|
72
|
-
it('
|
|
96
|
+
it('agent:bootstrap hook removes all bootstrap files', async () => {
|
|
97
|
+
const { api, internalHooks } = createMockOpenClawApi();
|
|
98
|
+
const { default: plugin } = await import('../index.js');
|
|
99
|
+
plugin.register(api as never);
|
|
100
|
+
|
|
101
|
+
const bootstrapHook = internalHooks.find(h => h.events === 'agent:bootstrap')!;
|
|
102
|
+
const event = {
|
|
103
|
+
type: 'agent',
|
|
104
|
+
action: 'bootstrap',
|
|
105
|
+
sessionKey: 'test',
|
|
106
|
+
context: {
|
|
107
|
+
workspaceDir: '/tmp/test',
|
|
108
|
+
bootstrapFiles: [
|
|
109
|
+
{ name: 'AGENTS.md', path: '/tmp/AGENTS.md', content: '# Agents', missing: false },
|
|
110
|
+
{ name: 'SOUL.md', path: '/tmp/SOUL.md', content: '# Soul', missing: false },
|
|
111
|
+
{ name: 'IDENTITY.md', path: '/tmp/IDENTITY.md', content: '# Identity', missing: false },
|
|
112
|
+
{ name: 'USER.md', path: '/tmp/USER.md', content: '# User', missing: false },
|
|
113
|
+
{ name: 'TOOLS.md', path: '/tmp/TOOLS.md', content: '# Tools', missing: false },
|
|
114
|
+
{ name: 'HEARTBEAT.md', path: '/tmp/HEARTBEAT.md', content: '', missing: false },
|
|
115
|
+
],
|
|
116
|
+
},
|
|
117
|
+
timestamp: new Date(),
|
|
118
|
+
messages: [],
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
bootstrapHook.handler(event);
|
|
122
|
+
expect(event.context.bootstrapFiles).toEqual([]);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('registers agent_end hook', async () => {
|
|
73
126
|
const { api, hooks } = createMockOpenClawApi();
|
|
74
127
|
const { default: plugin } = await import('../index.js');
|
|
75
128
|
plugin.register(api as never);
|
|
76
129
|
|
|
77
|
-
const
|
|
78
|
-
expect(
|
|
130
|
+
const agentEnd = hooks.find(h => h.event === 'agent_end');
|
|
131
|
+
expect(agentEnd).toBeDefined();
|
|
79
132
|
});
|
|
80
133
|
|
|
81
134
|
it('registers dot-ai service', async () => {
|
|
@@ -114,16 +167,16 @@ describe('OpenClaw Plugin Integration', () => {
|
|
|
114
167
|
});
|
|
115
168
|
});
|
|
116
169
|
|
|
117
|
-
describe('
|
|
170
|
+
describe('before_prompt_build → boot → processPrompt', () => {
|
|
118
171
|
it('skips sub-agent sessions', async () => {
|
|
119
172
|
const { api, hooks, logs } = createMockOpenClawApi();
|
|
120
173
|
const { default: plugin } = await import('../index.js');
|
|
121
174
|
plugin.register(api as never);
|
|
122
175
|
|
|
123
|
-
const
|
|
124
|
-
const result = await
|
|
125
|
-
{},
|
|
126
|
-
{ workspaceDir: '/tmp/test', sessionKey: 'session:subagent:1'
|
|
176
|
+
const beforeBuild = hooks.find(h => h.event === 'before_prompt_build')!;
|
|
177
|
+
const result = await beforeBuild.handler(
|
|
178
|
+
{ prompt: 'hello' },
|
|
179
|
+
{ workspaceDir: '/tmp/test', sessionKey: 'session:subagent:1' },
|
|
127
180
|
);
|
|
128
181
|
|
|
129
182
|
expect(result).toBeUndefined();
|
|
@@ -135,10 +188,10 @@ describe('OpenClaw Plugin Integration', () => {
|
|
|
135
188
|
const { default: plugin } = await import('../index.js');
|
|
136
189
|
plugin.register(api as never);
|
|
137
190
|
|
|
138
|
-
const
|
|
139
|
-
const result = await
|
|
140
|
-
{},
|
|
141
|
-
{ workspaceDir: '/tmp/test', sessionKey: 'session:cron:cleanup'
|
|
191
|
+
const beforeBuild = hooks.find(h => h.event === 'before_prompt_build')!;
|
|
192
|
+
const result = await beforeBuild.handler(
|
|
193
|
+
{ prompt: 'cleanup' },
|
|
194
|
+
{ workspaceDir: '/tmp/test', sessionKey: 'session:cron:cleanup' },
|
|
142
195
|
);
|
|
143
196
|
|
|
144
197
|
expect(result).toBeUndefined();
|
|
@@ -146,21 +199,20 @@ describe('OpenClaw Plugin Integration', () => {
|
|
|
146
199
|
});
|
|
147
200
|
|
|
148
201
|
it('skips when no workspaceDir and no .ai/ in cwd', async () => {
|
|
149
|
-
// Mock cwd to a directory without .ai/
|
|
150
202
|
const cwdSpy = vi.spyOn(process, 'cwd').mockReturnValue('/tmp');
|
|
151
203
|
|
|
152
204
|
const { api, hooks, logs } = createMockOpenClawApi();
|
|
153
205
|
const { default: plugin } = await import('../index.js');
|
|
154
206
|
plugin.register(api as never);
|
|
155
207
|
|
|
156
|
-
const
|
|
157
|
-
const result = await
|
|
158
|
-
{},
|
|
208
|
+
const beforeBuild = hooks.find(h => h.event === 'before_prompt_build')!;
|
|
209
|
+
const result = await beforeBuild.handler(
|
|
159
210
|
{ prompt: 'hello' },
|
|
211
|
+
{},
|
|
160
212
|
);
|
|
161
213
|
|
|
162
214
|
expect(result).toBeUndefined();
|
|
163
|
-
expect(logs.some(l => l.includes('No
|
|
215
|
+
expect(logs.some(l => l.includes('No workspace found'))).toBe(true);
|
|
164
216
|
|
|
165
217
|
cwdSpy.mockRestore();
|
|
166
218
|
});
|
package/src/index.ts
CHANGED
|
@@ -1,22 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* dot-ai OpenClaw plugin
|
|
2
|
+
* dot-ai OpenClaw plugin v8
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Full integration with OpenClaw hook system:
|
|
5
|
+
* - agent:bootstrap hook → removes ALL OpenClaw workspace files (dot-ai owns context)
|
|
6
|
+
* - before_prompt_build → injects dot-ai context (static → prependSystemContext, dynamic → prependContext)
|
|
7
|
+
* - agent_end → feeds response back to runtime
|
|
8
|
+
* - Tools → delegates to runtime capabilities (memory, tasks, etc.)
|
|
7
9
|
*/
|
|
8
10
|
import { createRequire } from 'node:module';
|
|
9
11
|
import { existsSync } from 'node:fs';
|
|
10
12
|
import { join } from 'node:path';
|
|
11
|
-
import { DotAiRuntime,
|
|
13
|
+
import { DotAiRuntime, assembleSections } from '@dot-ai/core';
|
|
14
|
+
import type { Section } from '@dot-ai/core';
|
|
12
15
|
|
|
13
16
|
const require = createRequire(import.meta.url);
|
|
14
17
|
const { version: PKG_VERSION } = require('../package.json') as { version: string };
|
|
15
18
|
|
|
16
|
-
//
|
|
19
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
20
|
+
// Inline OpenClaw plugin API types (avoid hard dependency on OpenClaw internals)
|
|
21
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
22
|
+
|
|
17
23
|
interface OpenClawLogger {
|
|
18
24
|
info(msg: string): void;
|
|
19
25
|
debug?(msg: string): void;
|
|
26
|
+
warn?(msg: string): void;
|
|
20
27
|
}
|
|
21
28
|
|
|
22
29
|
interface OpenClawPluginApi {
|
|
@@ -24,12 +31,14 @@ interface OpenClawPluginApi {
|
|
|
24
31
|
pluginConfig?: Record<string, unknown>;
|
|
25
32
|
on(
|
|
26
33
|
event: string,
|
|
27
|
-
handler: (
|
|
28
|
-
event: unknown,
|
|
29
|
-
ctx: { workspaceDir?: string; sessionKey?: string; prompt?: string },
|
|
30
|
-
) => Promise<{ prependContext?: string } | void> | void,
|
|
34
|
+
handler: (event: unknown, ctx: Record<string, unknown>) => Promise<Record<string, unknown> | void> | void,
|
|
31
35
|
options?: { priority?: number },
|
|
32
36
|
): void;
|
|
37
|
+
registerHook(
|
|
38
|
+
events: string | string[],
|
|
39
|
+
handler: (event: InternalHookEvent) => Promise<void> | void,
|
|
40
|
+
opts?: { name?: string; description?: string },
|
|
41
|
+
): void;
|
|
33
42
|
registerService(service: {
|
|
34
43
|
id: string;
|
|
35
44
|
start(ctx: { logger: OpenClawLogger }): void;
|
|
@@ -38,6 +47,22 @@ interface OpenClawPluginApi {
|
|
|
38
47
|
registerTool(tool: OpenClawTool | OpenClawToolFactory, opts?: { name?: string; names?: string[] }): void;
|
|
39
48
|
}
|
|
40
49
|
|
|
50
|
+
interface InternalHookEvent {
|
|
51
|
+
type: string;
|
|
52
|
+
action: string;
|
|
53
|
+
sessionKey: string;
|
|
54
|
+
context: Record<string, unknown>;
|
|
55
|
+
timestamp: Date;
|
|
56
|
+
messages: string[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface BootstrapFile {
|
|
60
|
+
name: string;
|
|
61
|
+
path: string;
|
|
62
|
+
content?: string;
|
|
63
|
+
missing: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
41
66
|
interface OpenClawToolResult {
|
|
42
67
|
content: Array<{ type: string; text: string }>;
|
|
43
68
|
details?: Record<string, unknown>;
|
|
@@ -53,17 +78,86 @@ interface OpenClawTool {
|
|
|
53
78
|
|
|
54
79
|
type OpenClawToolFactory = (ctx: Record<string, unknown>) => OpenClawTool | OpenClawTool[] | null;
|
|
55
80
|
|
|
56
|
-
//
|
|
81
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
82
|
+
// Runtime cache
|
|
83
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
84
|
+
|
|
57
85
|
let cachedRuntime: DotAiRuntime | null = null;
|
|
58
86
|
let cachedWorkspace: string | null = null;
|
|
59
87
|
|
|
60
|
-
/**
|
|
61
|
-
* Check if a directory contains a .ai/ workspace.
|
|
62
|
-
*/
|
|
63
88
|
function hasWorkspace(dir: string): boolean {
|
|
64
89
|
return existsSync(join(dir, '.ai'));
|
|
65
90
|
}
|
|
66
91
|
|
|
92
|
+
function resolveWorkspace(
|
|
93
|
+
configuredWorkspace: string | undefined,
|
|
94
|
+
ctxWorkspaceDir: string | undefined,
|
|
95
|
+
): string | null {
|
|
96
|
+
const cwd = process.cwd();
|
|
97
|
+
const cwdWorkspace = hasWorkspace(cwd) ? cwd : null;
|
|
98
|
+
const raw = cwdWorkspace ?? configuredWorkspace ?? ctxWorkspaceDir;
|
|
99
|
+
if (!raw) return null;
|
|
100
|
+
// Strip trailing .ai/ if present
|
|
101
|
+
return raw.endsWith('/.ai') || raw.endsWith('\\.ai') ? raw.slice(0, -4) : raw;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function ensureRuntime(
|
|
105
|
+
workspaceDir: string,
|
|
106
|
+
logger: OpenClawLogger,
|
|
107
|
+
): Promise<DotAiRuntime> {
|
|
108
|
+
if (cachedRuntime && cachedWorkspace === workspaceDir) {
|
|
109
|
+
return cachedRuntime;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
logger.info(`[dot-ai] workspaceRoot=${workspaceDir}`);
|
|
113
|
+
cachedRuntime = new DotAiRuntime({ workspaceRoot: workspaceDir });
|
|
114
|
+
await cachedRuntime.boot();
|
|
115
|
+
cachedWorkspace = workspaceDir;
|
|
116
|
+
logger.info(`[dot-ai] Runtime booted (v${PKG_VERSION})`);
|
|
117
|
+
|
|
118
|
+
const diag = cachedRuntime.diagnostics;
|
|
119
|
+
logger.info(`[dot-ai] extensions=${diag.extensions.length}, capabilities=${diag.capabilityCount}`);
|
|
120
|
+
if (diag.vocabularySize !== undefined) {
|
|
121
|
+
logger.info(`[dot-ai] Vocabulary size: ${diag.vocabularySize}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return cachedRuntime;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
128
|
+
// Section splitting: static (cacheable) vs dynamic (per-turn)
|
|
129
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Static sections = identity files + system section (trimStrategy: 'never').
|
|
133
|
+
* These rarely change and benefit from provider prompt caching.
|
|
134
|
+
*
|
|
135
|
+
* Dynamic sections = memory, skills, tasks, tools, project agents.
|
|
136
|
+
* These change based on the current prompt.
|
|
137
|
+
*/
|
|
138
|
+
function splitSections(sections: Section[]): { static: Section[]; dynamic: Section[] } {
|
|
139
|
+
const staticSections: Section[] = [];
|
|
140
|
+
const dynamicSections: Section[] = [];
|
|
141
|
+
|
|
142
|
+
for (const section of sections) {
|
|
143
|
+
if (section.trimStrategy === 'never') {
|
|
144
|
+
staticSections.push(section);
|
|
145
|
+
} else {
|
|
146
|
+
dynamicSections.push(section);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Sort each group by priority DESC
|
|
151
|
+
staticSections.sort((a, b) => b.priority - a.priority);
|
|
152
|
+
dynamicSections.sort((a, b) => b.priority - a.priority);
|
|
153
|
+
|
|
154
|
+
return { static: staticSections, dynamic: dynamicSections };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
158
|
+
// Plugin definition
|
|
159
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
160
|
+
|
|
67
161
|
const plugin = {
|
|
68
162
|
id: 'dot-ai',
|
|
69
163
|
name: 'dot-ai — Universal AI Workspace Convention',
|
|
@@ -74,19 +168,40 @@ const plugin = {
|
|
|
74
168
|
register(api: OpenClawPluginApi) {
|
|
75
169
|
api.logger.info(`[dot-ai] Plugin loaded (v${PKG_VERSION})`);
|
|
76
170
|
|
|
77
|
-
// Capture plugin config workspace for use in before_agent_start handler.
|
|
78
|
-
// Set in openclaw.json: plugins.entries.dot-ai.config.workspace = "/path/to/project"
|
|
79
171
|
const configuredWorkspace = api.pluginConfig?.workspace as string | undefined;
|
|
80
172
|
if (configuredWorkspace) {
|
|
81
173
|
api.logger.info(`[dot-ai] workspace from config: ${configuredWorkspace}`);
|
|
82
174
|
}
|
|
83
175
|
|
|
84
|
-
//
|
|
176
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
177
|
+
// Internal Hook: agent:bootstrap — remove ALL OpenClaw workspace files
|
|
178
|
+
// dot-ai owns the full context; OpenClaw's AGENTS.md, SOUL.md, etc. are stubs.
|
|
179
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
180
|
+
|
|
181
|
+
api.registerHook(
|
|
182
|
+
'agent:bootstrap',
|
|
183
|
+
(event: InternalHookEvent) => {
|
|
184
|
+
if (event.type !== 'agent' || event.action !== 'bootstrap') return;
|
|
185
|
+
const ctx = event.context as { bootstrapFiles?: BootstrapFile[] };
|
|
186
|
+
if (!Array.isArray(ctx.bootstrapFiles)) return;
|
|
187
|
+
|
|
188
|
+
const removed = ctx.bootstrapFiles.map(f => f.name);
|
|
189
|
+
// Clear ALL bootstrap files — dot-ai provides everything
|
|
190
|
+
ctx.bootstrapFiles = [];
|
|
191
|
+
|
|
192
|
+
api.logger.debug?.(`[dot-ai] Removed ${removed.length} OpenClaw bootstrap files: ${removed.join(', ')}`);
|
|
193
|
+
},
|
|
194
|
+
{ name: 'dot-ai-bootstrap-filter', description: 'Remove OpenClaw workspace files — dot-ai owns context' },
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
198
|
+
// Tools: delegate to runtime capabilities
|
|
199
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
200
|
+
|
|
85
201
|
api.registerTool(
|
|
86
202
|
(_ctx: Record<string, unknown>) => {
|
|
87
203
|
if (!cachedRuntime?.isBooted) return null;
|
|
88
|
-
|
|
89
|
-
return capabilities.map((cap): OpenClawTool => ({
|
|
204
|
+
return cachedRuntime.capabilities.map((cap): OpenClawTool => ({
|
|
90
205
|
name: cap.name,
|
|
91
206
|
label: cap.name.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase()),
|
|
92
207
|
description: cap.description,
|
|
@@ -103,61 +218,56 @@ const plugin = {
|
|
|
103
218
|
{ names: ['memory_recall', 'memory_store', 'task_list', 'task_create', 'task_update'] },
|
|
104
219
|
);
|
|
105
220
|
|
|
106
|
-
//
|
|
221
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
222
|
+
// Plugin Hook: before_prompt_build — inject dot-ai context
|
|
223
|
+
// Replaces legacy before_agent_start. Has access to messages[] and
|
|
224
|
+
// supports prependSystemContext for prompt caching.
|
|
225
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
226
|
+
|
|
107
227
|
api.on(
|
|
108
|
-
'
|
|
228
|
+
'before_prompt_build',
|
|
109
229
|
async (
|
|
110
230
|
_event: unknown,
|
|
111
|
-
ctx:
|
|
231
|
+
ctx: Record<string, unknown>,
|
|
112
232
|
) => {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
// 3. OpenClaw's ctx.workspaceDir — fallback
|
|
117
|
-
const cwd = process.cwd();
|
|
118
|
-
const cwdWorkspace = hasWorkspace(cwd) ? cwd : null;
|
|
119
|
-
const rawWorkspaceDir = cwdWorkspace ?? configuredWorkspace ?? ctx.workspaceDir;
|
|
120
|
-
|
|
121
|
-
if (!rawWorkspaceDir) {
|
|
122
|
-
api.logger.info('[dot-ai] No workspaceRoot configured, no .ai/ in cwd, no workspaceDir — skipping');
|
|
233
|
+
const workspaceDir = resolveWorkspace(configuredWorkspace, ctx.workspaceDir as string | undefined);
|
|
234
|
+
if (!workspaceDir) {
|
|
235
|
+
api.logger.info('[dot-ai] No workspace found — skipping');
|
|
123
236
|
return;
|
|
124
237
|
}
|
|
125
238
|
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
? rawWorkspaceDir.slice(0, -4)
|
|
129
|
-
: rawWorkspaceDir;
|
|
130
|
-
|
|
131
|
-
const isSubagent = ctx.sessionKey?.includes(':subagent:') || ctx.sessionKey?.includes(':cron:');
|
|
239
|
+
const sessionKey = ctx.sessionKey as string | undefined;
|
|
240
|
+
const isSubagent = sessionKey?.includes(':subagent:') || sessionKey?.includes(':cron:');
|
|
132
241
|
if (isSubagent) {
|
|
133
242
|
api.logger.debug?.('[dot-ai] Sub-agent/cron session, skipping');
|
|
134
243
|
return;
|
|
135
244
|
}
|
|
136
245
|
|
|
137
246
|
try {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
await cachedRuntime.boot();
|
|
142
|
-
cachedWorkspace = workspaceDir;
|
|
143
|
-
api.logger.info(`[dot-ai] Runtime booted (v${PKG_VERSION})`);
|
|
144
|
-
|
|
145
|
-
// Log extension diagnostics
|
|
146
|
-
const diag = cachedRuntime.diagnostics;
|
|
147
|
-
api.logger.info(`[dot-ai] extensions=${diag.extensions.length}, capabilities=${diag.capabilityCount}`);
|
|
148
|
-
if (diag.vocabularySize !== undefined) {
|
|
149
|
-
api.logger.info(`[dot-ai] Vocabulary size: ${diag.vocabularySize}`);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
247
|
+
const runtime = await ensureRuntime(workspaceDir, api.logger);
|
|
248
|
+
const prompt = ((_event as { prompt?: string })?.prompt) ?? '';
|
|
249
|
+
const { sections } = await runtime.processPrompt(prompt);
|
|
152
250
|
|
|
153
|
-
|
|
154
|
-
const { sections } = await cachedRuntime.processPrompt(prompt);
|
|
155
|
-
const formatted = formatSections(sections);
|
|
251
|
+
if (sections.length === 0) return;
|
|
156
252
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
253
|
+
const { static: staticSections, dynamic: dynamicSections } = splitSections(sections);
|
|
254
|
+
|
|
255
|
+
const result: Record<string, string> = {};
|
|
256
|
+
|
|
257
|
+
// Static context → prependSystemContext (cached by providers like Anthropic)
|
|
258
|
+
if (staticSections.length > 0) {
|
|
259
|
+
result.prependSystemContext = assembleSections(staticSections);
|
|
160
260
|
}
|
|
261
|
+
|
|
262
|
+
// Dynamic context → prependContext (per-turn, changes with each prompt)
|
|
263
|
+
if (dynamicSections.length > 0) {
|
|
264
|
+
result.prependContext = assembleSections(dynamicSections);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const totalSections = staticSections.length + dynamicSections.length;
|
|
268
|
+
api.logger.info(`[dot-ai] Injected: ${totalSections} sections (${staticSections.length} cached, ${dynamicSections.length} per-turn)`);
|
|
269
|
+
|
|
270
|
+
return result;
|
|
161
271
|
} catch (err) {
|
|
162
272
|
api.logger.info(`[dot-ai] Pipeline error: ${err}`);
|
|
163
273
|
}
|
|
@@ -166,8 +276,11 @@ const plugin = {
|
|
|
166
276
|
{ priority: 10 },
|
|
167
277
|
);
|
|
168
278
|
|
|
169
|
-
//
|
|
170
|
-
|
|
279
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
280
|
+
// Plugin Hook: agent_end — feed response back to runtime
|
|
281
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
282
|
+
|
|
283
|
+
api.on('agent_end', async (_event, ctx) => {
|
|
171
284
|
if (!cachedRuntime) return;
|
|
172
285
|
const response = (ctx as { response?: string }).response ?? '';
|
|
173
286
|
if (response) {
|
|
@@ -175,7 +288,10 @@ const plugin = {
|
|
|
175
288
|
}
|
|
176
289
|
});
|
|
177
290
|
|
|
291
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
178
292
|
// Service registration
|
|
293
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
294
|
+
|
|
179
295
|
api.registerService({
|
|
180
296
|
id: 'dot-ai',
|
|
181
297
|
start: (svc) => svc.logger.info(`[dot-ai] Active (workspace: ${configuredWorkspace ?? 'cwd'})`),
|