@dot-ai/core 0.9.0 → 0.10.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.
Files changed (43) hide show
  1. package/dist/boot-cache.d.ts +1 -1
  2. package/dist/boot-cache.d.ts.map +1 -1
  3. package/dist/extension-api.d.ts +10 -9
  4. package/dist/extension-api.d.ts.map +1 -1
  5. package/dist/extension-loader.d.ts +1 -1
  6. package/dist/extension-loader.d.ts.map +1 -1
  7. package/dist/extension-loader.js +20 -7
  8. package/dist/extension-loader.js.map +1 -1
  9. package/dist/extension-runner.d.ts +9 -4
  10. package/dist/extension-runner.d.ts.map +1 -1
  11. package/dist/extension-runner.js +34 -13
  12. package/dist/extension-runner.js.map +1 -1
  13. package/dist/extension-types.d.ts +15 -115
  14. package/dist/extension-types.d.ts.map +1 -1
  15. package/dist/extension-types.js +1 -88
  16. package/dist/extension-types.js.map +1 -1
  17. package/dist/format.d.ts +21 -0
  18. package/dist/format.d.ts.map +1 -1
  19. package/dist/format.js +74 -0
  20. package/dist/format.js.map +1 -1
  21. package/dist/index.d.ts +4 -5
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +2 -3
  24. package/dist/index.js.map +1 -1
  25. package/dist/runtime.d.ts +31 -43
  26. package/dist/runtime.d.ts.map +1 -1
  27. package/dist/runtime.js +60 -176
  28. package/dist/runtime.js.map +1 -1
  29. package/package.json +1 -1
  30. package/src/__tests__/extension-runner.test.ts +245 -32
  31. package/src/__tests__/fixtures/extensions/ctx-aware.js +12 -3
  32. package/src/__tests__/fixtures/extensions/smart-context.js +12 -3
  33. package/src/__tests__/format.test.ts +178 -1
  34. package/src/__tests__/runtime.test.ts +38 -10
  35. package/src/boot-cache.ts +1 -1
  36. package/src/extension-api.ts +10 -15
  37. package/src/extension-loader.ts +19 -10
  38. package/src/extension-runner.ts +44 -15
  39. package/src/extension-types.ts +26 -195
  40. package/src/format.ts +100 -0
  41. package/src/index.ts +3 -11
  42. package/src/runtime.ts +73 -221
  43. package/tsconfig.tsbuildinfo +1 -1
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @dot-ai/core v6 — Headless Agent framework.
2
+ * @dot-ai/core v7 — Headless Agent framework.
3
3
  *
4
4
  * dot-ai = extensions (event-driven plugins) + adapters (agent integration).
5
5
  * Everything is an extension. Core orchestrates events, adapters map to agent runtimes.
@@ -29,8 +29,6 @@ export type {
29
29
  // ── Extension Types ──
30
30
  export type {
31
31
  Section,
32
- ResourceEntry,
33
- ResourcesDiscoverResult,
34
32
  LabelExtractEvent,
35
33
  ContextEnrichEvent,
36
34
  ContextEnrichResult,
@@ -45,18 +43,12 @@ export type {
45
43
  ToolDefinition,
46
44
  ExtensionContext,
47
45
  ExtensionEvent,
48
- ExtensionTier,
49
- ExtensionEventName,
50
46
  LoadedExtension,
51
47
  ExtensionDiagnostic,
52
48
  ToolCallEvent, ToolCallResult,
53
49
  ToolResultEvent,
54
50
  AgentEndEvent,
55
- Message,
56
- ContextInjectEvent, ContextInjectResult,
57
- ContextModifyEvent, ContextModifyResult,
58
51
  } from './extension-types.js';
59
- export { EVENT_TIERS, ADAPTER_CAPABILITIES, TOOL_STRATEGY } from './extension-types.js';
60
52
 
61
53
  // ── Extension Runner ──
62
54
  export { ExtensionRunner, EventBus } from './extension-runner.js';
@@ -78,8 +70,8 @@ export { extractLabels, buildVocabulary } from './labels.js';
78
70
  export { discoverNodes, parseScanDirs } from './nodes.js';
79
71
 
80
72
  // ── Format ──
81
- export { formatContext, formatToolHints } from './format.js';
82
- export type { FormatOptions } from './format.js';
73
+ export { formatContext, formatToolHints, formatSections, assembleSections, trimSections } from './format.js';
74
+ export type { FormatOptions, FormatSectionsOptions } from './format.js';
83
75
 
84
76
  // ── Capabilities ──
85
77
  export { toolDefinitionToCapability } from './capabilities.js';
package/src/runtime.ts CHANGED
@@ -1,19 +1,16 @@
1
1
  import { loadConfig } from './config.js';
2
- import { computeChecksum, loadBootCache, writeBootCache } from './boot-cache.js';
3
- import type { FormatOptions } from './format.js';
4
2
  import { toolDefinitionToCapability } from './capabilities.js';
5
3
  import type { Capability } from './capabilities.js';
6
4
  import { discoverExtensions, createV6CollectorAPI } from './extension-loader.js';
7
5
  import { ExtensionRunner, EventBus } from './extension-runner.js';
8
6
  import type {
9
7
  ToolCallEvent, ToolCallResult, ExtensionDiagnostic,
10
- ContextInjectEvent, ContextInjectResult,
11
- Section, ResourcesDiscoverResult, ContextEnrichEvent,
8
+ Section, ContextEnrichEvent,
12
9
  RouteEvent, RouteResult,
13
- CommandDefinition,
10
+ CommandDefinition, ToolDefinition,
14
11
  } from './extension-types.js';
15
12
  import type { ExtensionContextV6 } from './extension-api.js';
16
- import type { EnrichedContext, ExtensionsConfig, Label, BudgetWarning } from './types.js';
13
+ import type { Label, Skill, Identity, ExtensionsConfig } from './types.js';
17
14
  import type { Logger } from './logger.js';
18
15
  import { NoopLogger } from './logger.js';
19
16
  import { extractLabels } from './labels.js';
@@ -23,38 +20,31 @@ export interface RuntimeOptions {
23
20
  workspaceRoot: string;
24
21
  /** Optional logger */
25
22
  logger?: Logger;
26
- /** Skip identity sections in formatted output (useful when adapter injects them separately) */
27
- skipIdentities?: boolean;
28
- /** Max skills in formatted output */
29
- maxSkills?: number;
30
- /** Max chars per skill */
31
- maxSkillLength?: number;
32
23
  /** Token budget for formatted output */
33
24
  tokenBudget?: number;
34
25
  /** Extension configuration */
35
26
  extensions?: ExtensionsConfig;
36
27
  }
37
28
 
29
+ /**
30
+ * v7 ProcessResult — structured data only.
31
+ * Adapters handle formatting via formatSections() utility.
32
+ */
38
33
  export interface ProcessResult {
39
- /** The formatted context string ready for injection */
40
- formatted: string;
41
- /** The enriched context (for adapters that need raw data) */
42
- enriched: EnrichedContext;
43
- /** Interactive capabilities built from extensions */
44
- capabilities: Capability[];
45
- /** Routing result from route event */
46
- routing?: RouteResult | null;
34
+ /** Sections collected from extensions, sorted by priority DESC */
35
+ sections: Section[];
47
36
  /** Matched labels */
48
- labels?: Label[];
49
- /** Sections collected from extensions */
50
- sections?: Section[];
37
+ labels: Label[];
38
+ /** Routing result from route event */
39
+ routing: RouteResult | null;
51
40
  }
52
41
 
53
42
  export interface RuntimeDiagnostics {
54
43
  extensions: ExtensionDiagnostic[];
55
- usedTiers: string[];
56
44
  capabilityCount: number;
57
45
  vocabularySize: number;
46
+ skillCount: number;
47
+ identityCount: number;
58
48
  }
59
49
 
60
50
  /**
@@ -90,7 +80,7 @@ export class DotAiRuntime {
90
80
  // ══════════════════════════════════════════════════════════════════════════════
91
81
 
92
82
  /**
93
- * Boot the runtime — loads config, discovers extensions, fires lifecycle events.
83
+ * Boot the runtime — loads config, discovers extensions, builds vocabulary from registrations.
94
84
  * Call once per session. Safe to call multiple times (idempotent).
95
85
  */
96
86
  async boot(): Promise<void> {
@@ -106,54 +96,14 @@ export class DotAiRuntime {
106
96
  const extConfig = this.options.extensions ?? rawConfig.extensions;
107
97
  const extPaths = await discoverExtensions(this.options.workspaceRoot, extConfig);
108
98
 
109
- // Compute checksum for cache invalidation
110
- const checksum = await computeChecksum(this.options.workspaceRoot, extPaths);
111
-
112
- // Try loading from cache
113
- const cached = await loadBootCache(this.options.workspaceRoot, checksum);
114
-
115
- // Load extensions via v6 collector API (always needed for handlers)
99
+ // Load extensions via collector API
100
+ // Extension factories run here — they call registerSkill(), registerIdentity(),
101
+ // contributeLabels(), registerTool(), registerCommand(), and subscribe to events.
116
102
  const loaded = await this.loadExtensions(extPaths);
117
103
  this._runner = new ExtensionRunner(loaded, this.logger);
118
104
 
119
- if (cached) {
120
- // Cache hit — use cached vocabulary, skip resources_discover
121
- this.vocabulary = cached.vocabulary;
122
- this.logger.log({
123
- timestamp: new Date().toISOString(),
124
- level: 'info',
125
- phase: 'boot',
126
- event: 'boot_cache_hit',
127
- data: { checksum, vocabularySize: cached.vocabulary.length },
128
- });
129
- } else {
130
- // Cache miss — fire resources_discover → build vocabulary
131
- const ctx = this.buildCtx();
132
- const discoverResults = await this._runner.fire<ResourcesDiscoverResult>(
133
- 'resources_discover', undefined, ctx,
134
- );
135
-
136
- // Build vocabulary from all extension-contributed labels
137
- const allLabels = new Set<string>();
138
- for (const result of discoverResults) {
139
- if (result.labels && Array.isArray(result.labels)) {
140
- for (const label of result.labels) {
141
- allLabels.add(label);
142
- }
143
- }
144
- }
145
- this.vocabulary = Array.from(allLabels);
146
-
147
- // Write cache
148
- await writeBootCache(this.options.workspaceRoot, {
149
- version: 1,
150
- checksum,
151
- vocabulary: this.vocabulary,
152
- extensionPaths: extPaths,
153
- tools: this._runner.tools.map(t => ({ name: t.name, description: t.description ?? '' })),
154
- createdAt: new Date().toISOString(),
155
- });
156
- }
105
+ // Build vocabulary from registered resources (replaces resources_discover)
106
+ this.vocabulary = this._runner.vocabularyLabels;
157
107
 
158
108
  // Build capabilities from extension-registered tools
159
109
  this.caps = this._runner.tools.map(toolDefinitionToCapability);
@@ -168,12 +118,13 @@ export class DotAiRuntime {
168
118
  vocabularySize: this.vocabulary.length,
169
119
  toolCount: this._runner.tools.length,
170
120
  commandCount: this._runner.commands.length,
171
- cacheHit: !!cached,
121
+ skillCount: this._runner.skills.length,
122
+ identityCount: this._runner.identities.length,
172
123
  },
173
124
  durationMs: Math.round(performance.now() - start),
174
125
  });
175
126
 
176
- // Fire session_start (always, regardless of cache)
127
+ // Fire session_start (always)
177
128
  const ctx = this.buildCtx();
178
129
  await this._runner.fire('session_start', undefined, ctx);
179
130
 
@@ -244,6 +195,8 @@ export class DotAiRuntime {
244
195
  ),
245
196
  tools: Array.from(extension.tools.keys()),
246
197
  commands: Array.from(extension.commands.keys()),
198
+ skills: Array.from(extension.skills.keys()),
199
+ identities: Array.from(extension.identities.keys()),
247
200
  },
248
201
  });
249
202
  } catch (err) {
@@ -271,11 +224,12 @@ export class DotAiRuntime {
271
224
  * Process a prompt through the pipeline:
272
225
  * 1. Extract labels (core regex + label_extract chain-transform)
273
226
  * 2. Route (first-result)
274
- * 3. Context enrich (collect-sections)
275
- * 4. Assemble sections by priority
276
- * 5. Apply token budget trimming
227
+ * 3. Context enrich (collect-sections) + core system section
228
+ * 4. Sort sections by priority DESC
229
+ *
230
+ * Returns structured data. Adapters call formatSections() to get markdown.
277
231
  */
278
- async processPrompt(prompt: string, formatOverrides?: Partial<FormatOptions>): Promise<ProcessResult> {
232
+ async processPrompt(prompt: string): Promise<ProcessResult> {
279
233
  if (!this.booted) {
280
234
  await this.boot();
281
235
  }
@@ -320,77 +274,57 @@ export class DotAiRuntime {
320
274
  const ctx = this.buildCtx(labels);
321
275
  const collected = await this._runner.fireCollectSections('context_enrich', enrichEvent, ctx);
322
276
  sections = collected.sections;
277
+ }
323
278
 
324
- // Also fire legacy context_inject for backward compat
325
- const injectEvent: ContextInjectEvent = { prompt, labels };
326
- const injectResults = await this._runner.fire<ContextInjectResult>(
327
- 'context_inject', injectEvent, ctx,
328
- );
329
- for (const result of injectResults) {
330
- if (result.inject) {
331
- sections.push({
332
- id: `legacy-inject-${sections.length}`,
333
- title: 'Extension Context',
334
- content: result.inject,
335
- priority: 20,
336
- source: 'legacy',
337
- });
338
- }
339
- }
279
+ // Add core system section
280
+ if (this._runner) {
281
+ const skillNames = this._runner.skills.map(s => s.name);
282
+ const toolNames = this._runner.tools.map(t => t.name);
283
+ const parts = ['dot-ai workspace active.'];
284
+ if (skillNames.length > 0) parts.push(`Skills: ${skillNames.join(', ')}.`);
285
+ if (toolNames.length > 0) parts.push(`Tools: ${toolNames.join(', ')}.`);
286
+
287
+ sections.push({
288
+ id: 'dot-ai:system',
289
+ title: 'dot-ai',
290
+ content: parts.join(' '),
291
+ priority: 95,
292
+ source: 'core',
293
+ trimStrategy: 'never',
294
+ });
340
295
  }
341
296
 
342
297
  // 4. Sort sections by priority DESC
343
298
  sections.sort((a, b) => b.priority - a.priority);
344
299
 
345
- // 5. Apply token budget trimming
346
- const tokenBudget = formatOverrides?.tokenBudget ?? this.options.tokenBudget;
347
- if (tokenBudget) {
348
- sections = this.trimSections(sections, tokenBudget);
349
- }
350
-
351
- // 6. Assemble sections into formatted markdown
352
- const formatted = this.assembleSections(sections);
353
-
354
300
  this.logger.log({
355
301
  timestamp: new Date().toISOString(),
356
302
  level: 'info',
357
- phase: 'format',
358
- event: 'format_complete',
303
+ phase: 'enrich',
304
+ event: 'prompt_processed',
359
305
  data: {
360
306
  sectionCount: sections.length,
361
- outputChars: formatted.length,
362
- estimatedTokens: Math.round(formatted.length / 4),
363
307
  routing: routing?.model ?? 'default',
364
308
  labels: labels.map(l => l.name),
365
309
  },
366
310
  durationMs: Math.round(performance.now() - start),
367
311
  });
368
312
 
369
- // Build enriched context for adapters that need it
370
- const enriched = this.buildEnrichedFromSections(prompt, labels, sections, routing);
371
-
372
- return {
373
- formatted,
374
- enriched,
375
- capabilities: this.caps,
376
- routing,
377
- labels,
378
- sections,
379
- };
313
+ return { sections, labels, routing };
380
314
  }
381
315
 
382
316
  // ══════════════════════════════════════════════════════════════════════════════
383
- // Learn
317
+ // Tool Execution
384
318
  // ══════════════════════════════════════════════════════════════════════════════
385
319
 
386
320
  /**
387
- * Learn from agent response fires agent_end event.
388
- * Memory extensions handle storage in their handlers.
321
+ * Execute a registered tool by name.
389
322
  */
390
- async learn(response: string): Promise<void> {
391
- if (this._runner) {
392
- await this._runner.fire('agent_end', { response }, this.buildCtx());
393
- }
323
+ async executeTool(name: string, input: Record<string, unknown>): Promise<{ content: string; details?: unknown; isError?: boolean }> {
324
+ if (!this._runner) throw new Error('Runtime not booted');
325
+ const tool = this._runner.tools.find(t => t.name === name);
326
+ if (!tool) throw new Error(`Tool not found: ${name}`);
327
+ return tool.execute(input, this.buildCtx());
394
328
  }
395
329
 
396
330
  // ══════════════════════════════════════════════════════════════════════════════
@@ -452,111 +386,29 @@ export class DotAiRuntime {
452
386
  return this._runner?.commands ?? [];
453
387
  }
454
388
 
455
- /** Get diagnostics including extensions */
456
- get diagnostics(): RuntimeDiagnostics {
457
- return {
458
- extensions: this._runner?.diagnostics ?? [],
459
- usedTiers: this._runner ? Array.from(this._runner.usedTiers) : [],
460
- capabilityCount: this.caps.length,
461
- vocabularySize: this.vocabulary.length,
462
- };
389
+ /** Get registered skills from extensions */
390
+ get skills(): Skill[] {
391
+ return this._runner?.skills ?? [];
463
392
  }
464
393
 
465
- // ══════════════════════════════════════════════════════════════════════════════
466
- // Helpers
467
- // ══════════════════════════════════════════════════════════════════════════════
468
-
469
- /**
470
- * Assemble sorted sections into a single markdown string.
471
- */
472
- private assembleSections(sections: Section[]): string {
473
- if (sections.length === 0) return '';
474
-
475
- return sections
476
- .map(s => {
477
- if (s.title) {
478
- return `## ${s.title}\n\n${s.content}`;
479
- }
480
- return s.content;
481
- })
482
- .join('\n\n---\n\n');
394
+ /** Get registered identities from extensions */
395
+ get identities(): Identity[] {
396
+ return this._runner?.identities ?? [];
483
397
  }
484
398
 
485
- /**
486
- * Trim sections to fit within a token budget.
487
- * Respects trimStrategy: 'never' sections are never removed.
488
- */
489
- private trimSections(sections: Section[], budget: number): Section[] {
490
- const estimateTokens = (secs: Section[]) =>
491
- Math.round(secs.map(s => `## ${s.title}\n\n${s.content}`).join('\n\n---\n\n').length / 4);
492
-
493
- let current = estimateTokens(sections);
494
- if (current <= budget) return sections;
495
-
496
- const result = [...sections];
497
- const actions: string[] = [];
498
-
499
- // Strategy 1: Truncate 'truncate' sections
500
- for (let i = result.length - 1; i >= 0; i--) {
501
- if (current <= budget) break;
502
- const s = result[i];
503
- if (s.trimStrategy === 'truncate' && s.content.length > 2000) {
504
- result[i] = { ...s, content: s.content.slice(0, 2000) + '\n\n[...truncated]' };
505
- actions.push(`truncated section: ${s.id}`);
506
- current = estimateTokens(result);
507
- }
508
- }
509
-
510
- // Strategy 2: Drop non-'never' sections (lowest priority first)
511
- for (let i = result.length - 1; i >= 0; i--) {
512
- if (current <= budget) break;
513
- const s = result[i];
514
- if (s.trimStrategy !== 'never') {
515
- actions.push(`dropped section: ${s.id} (priority ${s.priority})`);
516
- result.splice(i, 1);
517
- current = estimateTokens(result);
518
- }
519
- }
520
-
521
- if (actions.length > 0) {
522
- const warning: BudgetWarning = { budget, actual: current, actions };
523
- this.logger.log({
524
- timestamp: new Date().toISOString(),
525
- level: current > budget ? 'warn' : 'info',
526
- phase: 'format',
527
- event: 'budget_trimmed',
528
- data: warning as unknown as Record<string, unknown>,
529
- });
530
- }
531
-
532
- return result;
399
+ /** Get registered tools from extensions */
400
+ get tools(): ToolDefinition[] {
401
+ return this._runner?.tools ?? [];
533
402
  }
534
403
 
535
- /**
536
- * Build a backward-compatible EnrichedContext from pipeline output.
537
- * Adapters that log enriched fields still work.
538
- */
539
- private buildEnrichedFromSections(
540
- prompt: string,
541
- labels: Label[],
542
- sections: Section[],
543
- routing: RouteResult | null,
544
- ): EnrichedContext {
404
+ /** Get diagnostics including extensions */
405
+ get diagnostics(): RuntimeDiagnostics {
545
406
  return {
546
- prompt,
547
- labels,
548
- identities: sections
549
- .filter(s => s.source === 'identity' || s.priority >= 80)
550
- .map(s => ({
551
- type: s.id ?? s.source,
552
- content: s.content,
553
- source: s.source,
554
- priority: s.priority,
555
- })),
556
- memories: [],
557
- skills: [],
558
- tools: [],
559
- routing: routing ?? { model: 'default', reason: 'no routing extensions' },
407
+ extensions: this._runner?.diagnostics ?? [],
408
+ capabilityCount: this.caps.length,
409
+ vocabularySize: this.vocabulary.length,
410
+ skillCount: this._runner?.skills.length ?? 0,
411
+ identityCount: this._runner?.identities.length ?? 0,
560
412
  };
561
413
  }
562
414
  }