@auxiora/intent 1.0.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.
@@ -0,0 +1,55 @@
1
+ import type { Intent } from './types.js';
2
+ import { IntentParser } from './parser.js';
3
+
4
+ const COMPOUND_SPLITTERS = [
5
+ / and then /i,
6
+ / then /i,
7
+ / after that /i,
8
+ / also /i,
9
+ /,\s*and /,
10
+ /;\s*/,
11
+ ];
12
+
13
+ export class IntentDecomposer {
14
+ private parser: IntentParser;
15
+
16
+ constructor(parser: IntentParser) {
17
+ this.parser = parser;
18
+ }
19
+
20
+ decompose(message: string, context?: Record<string, unknown>): Intent[] {
21
+ const parts = this.splitCompound(message);
22
+
23
+ if (parts.length <= 1) {
24
+ return [this.parser.parse(message, context)];
25
+ }
26
+
27
+ return parts.map((part) => this.parser.parse(part.trim(), context));
28
+ }
29
+
30
+ isCompound(message: string): boolean {
31
+ return this.splitCompound(message).length > 1;
32
+ }
33
+
34
+ private splitCompound(message: string): string[] {
35
+ let parts = [message];
36
+
37
+ for (const splitter of COMPOUND_SPLITTERS) {
38
+ const newParts: string[] = [];
39
+ for (const part of parts) {
40
+ const split = part.split(splitter);
41
+ if (split.length > 1) {
42
+ newParts.push(...split.filter((s) => s.trim().length > 0));
43
+ } else {
44
+ newParts.push(part);
45
+ }
46
+ }
47
+ if (newParts.length > parts.length) {
48
+ parts = newParts;
49
+ break; // Use first matching splitter only
50
+ }
51
+ }
52
+
53
+ return parts;
54
+ }
55
+ }
package/src/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ export type {
2
+ IntentType,
3
+ IntentEntity,
4
+ ActionStep,
5
+ Intent,
6
+ IntentParserConfig,
7
+ } from './types.js';
8
+ export { DEFAULT_INTENT_PARSER_CONFIG } from './types.js';
9
+ export { IntentParser } from './parser.js';
10
+ export { ActionPlanner } from './planner.js';
11
+ export { IntentDecomposer } from './decomposer.js';
package/src/parser.ts ADDED
@@ -0,0 +1,140 @@
1
+ import type { Intent, IntentType, IntentEntity, IntentParserConfig } from './types.js';
2
+ import { DEFAULT_INTENT_PARSER_CONFIG } from './types.js';
3
+
4
+ interface KeywordRule {
5
+ type: IntentType;
6
+ keywords: string[];
7
+ weight: number;
8
+ }
9
+
10
+ const KEYWORD_RULES: KeywordRule[] = [
11
+ { type: 'send_message', keywords: ['send', 'message', 'tell', 'notify', 'text', 'dm', 'reply'], weight: 1 },
12
+ { type: 'read_message', keywords: ['read', 'check', 'inbox', 'unread', 'messages'], weight: 1 },
13
+ { type: 'search', keywords: ['search', 'find', 'look up', 'lookup'], weight: 1 },
14
+ { type: 'create_file', keywords: ['create', 'new file', 'write file', 'make file', 'generate'], weight: 1 },
15
+ { type: 'read_file', keywords: ['read file', 'open file', 'show file', 'view'], weight: 1 },
16
+ { type: 'edit_file', keywords: ['edit', 'modify', 'update file', 'change file'], weight: 1 },
17
+ { type: 'delete_file', keywords: ['delete', 'remove', 'trash'], weight: 1 },
18
+ { type: 'browse_web', keywords: ['browse', 'visit', 'open url', 'navigate', 'website', 'go to'], weight: 1 },
19
+ { type: 'run_command', keywords: ['run', 'command', 'terminal'], weight: 1 },
20
+ { type: 'schedule', keywords: ['schedule', 'calendar', 'appointment', 'meeting', 'book'], weight: 1 },
21
+ { type: 'remind', keywords: ['remind', 'reminder', 'alarm', 'alert me'], weight: 1 },
22
+ { type: 'query', keywords: ['what is', 'who is', 'how to', 'explain', 'tell me about'], weight: 0.8 },
23
+ { type: 'summarize', keywords: ['summarize', 'summary', 'tldr', 'recap', 'brief'], weight: 1 },
24
+ { type: 'translate', keywords: ['translate', 'translation', 'in spanish', 'in french', 'in german'], weight: 1 },
25
+ { type: 'compose', keywords: ['compose', 'draft', 'write an email', 'write a letter', 'write a message'], weight: 1 },
26
+ { type: 'analyze', keywords: ['analyze', 'analysis', 'review', 'assess'], weight: 0.9 },
27
+ { type: 'configure', keywords: ['configure', 'settings', 'set up', 'config', 'preferences'], weight: 1 },
28
+ ];
29
+
30
+ const ENTITY_PATTERNS: Array<{ type: string; pattern: RegExp }> = [
31
+ { type: 'url', pattern: /https?:\/\/[^\s]+/gi },
32
+ { type: 'email', pattern: /[\w.-]+@[\w.-]+\.\w+/gi },
33
+ { type: 'file_path', pattern: /(?:\/[\w.-]+)+/g },
34
+ { type: 'time', pattern: /\b\d{1,2}:\d{2}(?:\s*(?:am|pm))?\b/gi },
35
+ { type: 'date', pattern: /\b(?:today|tomorrow|yesterday|\d{4}-\d{2}-\d{2})\b/gi },
36
+ { type: 'mention', pattern: /@[\w.-]+/g },
37
+ ];
38
+
39
+ const CONNECTOR_KEYWORDS: Record<string, string[]> = {
40
+ slack: ['slack', 'channel'],
41
+ discord: ['discord', 'server'],
42
+ email: ['email', 'mail', 'inbox', 'gmail'],
43
+ calendar: ['calendar', 'schedule', 'meeting', 'appointment'],
44
+ github: ['github', 'repository', 'repo', 'pull request', 'pr', 'issue'],
45
+ notion: ['notion', 'page', 'database'],
46
+ };
47
+
48
+ export class IntentParser {
49
+ private config: IntentParserConfig;
50
+
51
+ constructor(config?: Partial<IntentParserConfig>) {
52
+ this.config = { ...DEFAULT_INTENT_PARSER_CONFIG, ...config };
53
+ }
54
+
55
+ parse(message: string, context?: Record<string, unknown>): Intent {
56
+ const lower = message.toLowerCase();
57
+
58
+ // Score each intent type
59
+ const scores = new Map<IntentType, number>();
60
+
61
+ for (const rule of KEYWORD_RULES) {
62
+ let matchCount = 0;
63
+ for (const keyword of rule.keywords) {
64
+ if (lower.includes(keyword)) {
65
+ matchCount++;
66
+ }
67
+ }
68
+ if (matchCount > 0) {
69
+ // Base score of 0.5 for first match, increasing with more matches
70
+ const score = Math.min(0.5 + (matchCount - 1) * 0.15, 1) * rule.weight;
71
+ const current = scores.get(rule.type) ?? 0;
72
+ scores.set(rule.type, Math.max(current, score));
73
+ }
74
+ }
75
+
76
+ // Find best match
77
+ let bestType: IntentType = 'unknown';
78
+ let bestScore = 0;
79
+
80
+ for (const [type, score] of scores) {
81
+ if (score > bestScore) {
82
+ bestScore = score;
83
+ bestType = type;
84
+ }
85
+ }
86
+
87
+ // Apply confidence threshold
88
+ const confidence = Math.min(bestScore, 1);
89
+ if (confidence < this.config.confidenceThreshold) {
90
+ bestType = 'unknown';
91
+ }
92
+
93
+ // Extract entities
94
+ const entities = this.extractEntities(message);
95
+
96
+ // Detect required connectors
97
+ const requiredConnectors = this.detectConnectors(lower);
98
+
99
+ return {
100
+ type: bestType,
101
+ confidence: bestType === 'unknown' ? 0 : confidence,
102
+ entities,
103
+ requiredConnectors,
104
+ actionSteps: [],
105
+ rawText: message,
106
+ };
107
+ }
108
+
109
+ private extractEntities(text: string): IntentEntity[] {
110
+ const entities: IntentEntity[] = [];
111
+
112
+ for (const { type, pattern } of ENTITY_PATTERNS) {
113
+ // Reset regex state for global patterns
114
+ pattern.lastIndex = 0;
115
+ let match: RegExpExecArray | null;
116
+ while ((match = pattern.exec(text)) !== null) {
117
+ entities.push({
118
+ type,
119
+ value: match[0],
120
+ start: match.index,
121
+ end: match.index + match[0].length,
122
+ });
123
+ }
124
+ }
125
+
126
+ return entities;
127
+ }
128
+
129
+ private detectConnectors(text: string): string[] {
130
+ const connectors: string[] = [];
131
+
132
+ for (const [connector, keywords] of Object.entries(CONNECTOR_KEYWORDS)) {
133
+ if (keywords.some((kw) => text.includes(kw))) {
134
+ connectors.push(connector);
135
+ }
136
+ }
137
+
138
+ return connectors;
139
+ }
140
+ }
package/src/planner.ts ADDED
@@ -0,0 +1,155 @@
1
+ import * as crypto from 'node:crypto';
2
+ import type { Intent, ActionStep } from './types.js';
3
+
4
+ const INTENT_TO_DOMAIN: Record<string, string> = {
5
+ send_message: 'messaging',
6
+ read_message: 'messaging',
7
+ search: 'web',
8
+ create_file: 'files',
9
+ read_file: 'files',
10
+ edit_file: 'files',
11
+ delete_file: 'files',
12
+ browse_web: 'web',
13
+ run_command: 'shell',
14
+ schedule: 'calendar',
15
+ remind: 'calendar',
16
+ query: 'web',
17
+ summarize: 'web',
18
+ translate: 'web',
19
+ compose: 'messaging',
20
+ analyze: 'web',
21
+ configure: 'system',
22
+ };
23
+
24
+ function makeStep(action: string, domain: string, description: string, params: Record<string, unknown> = {}, dependsOn: string[] = []): ActionStep {
25
+ return {
26
+ id: crypto.randomUUID(),
27
+ action,
28
+ domain,
29
+ params,
30
+ dependsOn,
31
+ description,
32
+ };
33
+ }
34
+
35
+ export class ActionPlanner {
36
+ planActions(intent: Intent): ActionStep[] {
37
+ const domain = INTENT_TO_DOMAIN[intent.type] ?? 'system';
38
+ const steps: ActionStep[] = [];
39
+
40
+ switch (intent.type) {
41
+ case 'send_message': {
42
+ const recipient = intent.entities.find((e) => e.type === 'mention')?.value;
43
+ steps.push(makeStep('send_message', domain, `Send message${recipient ? ` to ${recipient}` : ''}`, {
44
+ recipient,
45
+ content: intent.rawText,
46
+ }));
47
+ break;
48
+ }
49
+
50
+ case 'read_message': {
51
+ steps.push(makeStep('read_messages', domain, 'Read recent messages'));
52
+ break;
53
+ }
54
+
55
+ case 'search': {
56
+ steps.push(makeStep('web_search', domain, 'Search the web', { query: intent.rawText }));
57
+ break;
58
+ }
59
+
60
+ case 'create_file': {
61
+ const filePath = intent.entities.find((e) => e.type === 'file_path')?.value;
62
+ steps.push(makeStep('create_file', domain, `Create file${filePath ? ` at ${filePath}` : ''}`, {
63
+ path: filePath,
64
+ }));
65
+ break;
66
+ }
67
+
68
+ case 'read_file': {
69
+ const filePath = intent.entities.find((e) => e.type === 'file_path')?.value;
70
+ steps.push(makeStep('read_file', domain, `Read file${filePath ? ` ${filePath}` : ''}`, {
71
+ path: filePath,
72
+ }));
73
+ break;
74
+ }
75
+
76
+ case 'edit_file': {
77
+ const filePath = intent.entities.find((e) => e.type === 'file_path')?.value;
78
+ const readStep = makeStep('read_file', domain, 'Read current file contents', { path: filePath });
79
+ const editStep = makeStep('edit_file', domain, 'Apply edits to file', { path: filePath }, [readStep.id]);
80
+ steps.push(readStep, editStep);
81
+ break;
82
+ }
83
+
84
+ case 'delete_file': {
85
+ const filePath = intent.entities.find((e) => e.type === 'file_path')?.value;
86
+ steps.push(makeStep('delete_file', domain, `Delete file${filePath ? ` ${filePath}` : ''}`, {
87
+ path: filePath,
88
+ }));
89
+ break;
90
+ }
91
+
92
+ case 'browse_web': {
93
+ const url = intent.entities.find((e) => e.type === 'url')?.value;
94
+ steps.push(makeStep('navigate', domain, `Browse to ${url ?? 'URL'}`, { url }));
95
+ break;
96
+ }
97
+
98
+ case 'run_command': {
99
+ steps.push(makeStep('run_command', domain, 'Run shell command', { command: intent.rawText }));
100
+ break;
101
+ }
102
+
103
+ case 'schedule': {
104
+ const date = intent.entities.find((e) => e.type === 'date')?.value;
105
+ const time = intent.entities.find((e) => e.type === 'time')?.value;
106
+ steps.push(makeStep('create_event', domain, 'Create calendar event', {
107
+ date,
108
+ time,
109
+ description: intent.rawText,
110
+ }));
111
+ break;
112
+ }
113
+
114
+ case 'remind': {
115
+ const date = intent.entities.find((e) => e.type === 'date')?.value;
116
+ const time = intent.entities.find((e) => e.type === 'time')?.value;
117
+ steps.push(makeStep('create_reminder', domain, 'Create reminder', {
118
+ date,
119
+ time,
120
+ description: intent.rawText,
121
+ }));
122
+ break;
123
+ }
124
+
125
+ case 'compose': {
126
+ const recipient = intent.entities.find((e) => e.type === 'email' || e.type === 'mention')?.value;
127
+ steps.push(makeStep('compose', domain, 'Compose message', {
128
+ recipient,
129
+ content: intent.rawText,
130
+ }));
131
+ break;
132
+ }
133
+
134
+ case 'summarize':
135
+ case 'translate':
136
+ case 'analyze':
137
+ case 'query': {
138
+ steps.push(makeStep(intent.type, domain, `${intent.type} content`, { input: intent.rawText }));
139
+ break;
140
+ }
141
+
142
+ case 'configure': {
143
+ steps.push(makeStep('configure', domain, 'Update configuration', { input: intent.rawText }));
144
+ break;
145
+ }
146
+
147
+ default: {
148
+ steps.push(makeStep('unknown', 'system', 'Process request', { input: intent.rawText }));
149
+ break;
150
+ }
151
+ }
152
+
153
+ return steps;
154
+ }
155
+ }
package/src/types.ts ADDED
@@ -0,0 +1,53 @@
1
+ export type IntentType =
2
+ | 'send_message'
3
+ | 'read_message'
4
+ | 'search'
5
+ | 'create_file'
6
+ | 'read_file'
7
+ | 'edit_file'
8
+ | 'delete_file'
9
+ | 'browse_web'
10
+ | 'run_command'
11
+ | 'schedule'
12
+ | 'remind'
13
+ | 'query'
14
+ | 'summarize'
15
+ | 'translate'
16
+ | 'compose'
17
+ | 'analyze'
18
+ | 'configure'
19
+ | 'unknown';
20
+
21
+ export interface IntentEntity {
22
+ type: string;
23
+ value: string;
24
+ start: number;
25
+ end: number;
26
+ }
27
+
28
+ export interface ActionStep {
29
+ id: string;
30
+ action: string;
31
+ domain: string;
32
+ params: Record<string, unknown>;
33
+ dependsOn: string[];
34
+ description: string;
35
+ }
36
+
37
+ export interface Intent {
38
+ type: IntentType;
39
+ confidence: number;
40
+ entities: IntentEntity[];
41
+ requiredConnectors: string[];
42
+ actionSteps: ActionStep[];
43
+ rawText: string;
44
+ }
45
+
46
+ export interface IntentParserConfig {
47
+ /** Minimum confidence threshold to accept a classification. */
48
+ confidenceThreshold: number;
49
+ }
50
+
51
+ export const DEFAULT_INTENT_PARSER_CONFIG: IntentParserConfig = {
52
+ confidenceThreshold: 0.3,
53
+ };
@@ -0,0 +1,46 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { IntentParser } from '../src/parser.js';
3
+ import { IntentDecomposer } from '../src/decomposer.js';
4
+
5
+ describe('IntentDecomposer', () => {
6
+ const parser = new IntentParser();
7
+ const decomposer = new IntentDecomposer(parser);
8
+
9
+ it('should return single intent for simple message', () => {
10
+ const intents = decomposer.decompose('Search for TypeScript tutorials');
11
+ expect(intents).toHaveLength(1);
12
+ expect(intents[0].type).toBe('search');
13
+ });
14
+
15
+ it('should detect compound intents with "and then"', () => {
16
+ const intents = decomposer.decompose('Search for the file and then delete it');
17
+ expect(intents.length).toBeGreaterThan(1);
18
+ });
19
+
20
+ it('should detect compound intents with "then"', () => {
21
+ const intents = decomposer.decompose('Search for the file then summarize it');
22
+ expect(intents.length).toBeGreaterThan(1);
23
+ });
24
+
25
+ it('should detect compound intents with semicolons', () => {
26
+ const intents = decomposer.decompose('Send a message; schedule a meeting');
27
+ expect(intents).toHaveLength(2);
28
+ });
29
+
30
+ it('should report compound status', () => {
31
+ expect(decomposer.isCompound('do something')).toBe(false);
32
+ expect(decomposer.isCompound('do this then do that')).toBe(true);
33
+ });
34
+
35
+ it('should parse each sub-intent independently', () => {
36
+ const intents = decomposer.decompose('Send a message; search for something');
37
+ expect(intents.length).toBe(2);
38
+ // Each should have rawText from its sub-part
39
+ expect(intents[0].rawText).not.toBe(intents[1].rawText);
40
+ });
41
+
42
+ it('should handle "after that"', () => {
43
+ const intents = decomposer.decompose('Read the inbox after that send a reply');
44
+ expect(intents.length).toBeGreaterThan(1);
45
+ });
46
+ });
@@ -0,0 +1,96 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { IntentParser } from '../src/parser.js';
3
+
4
+ describe('IntentParser', () => {
5
+ const parser = new IntentParser();
6
+
7
+ it('should parse send message intent', () => {
8
+ const result = parser.parse('Send a message to @john');
9
+ expect(result.type).toBe('send_message');
10
+ expect(result.confidence).toBeGreaterThan(0);
11
+ expect(result.entities.some((e) => e.type === 'mention')).toBe(true);
12
+ });
13
+
14
+ it('should parse search intent', () => {
15
+ const result = parser.parse('Search for TypeScript tutorials');
16
+ expect(result.type).toBe('search');
17
+ expect(result.confidence).toBeGreaterThan(0);
18
+ });
19
+
20
+ it('should parse browse web intent', () => {
21
+ const result = parser.parse('Navigate to https://example.com');
22
+ expect(result.type).toBe('browse_web');
23
+ expect(result.entities.some((e) => e.type === 'url')).toBe(true);
24
+ });
25
+
26
+ it('should parse schedule intent', () => {
27
+ const result = parser.parse('Schedule a meeting for tomorrow at 3:00 pm');
28
+ expect(result.type).toBe('schedule');
29
+ expect(result.entities.some((e) => e.type === 'date')).toBe(true);
30
+ expect(result.entities.some((e) => e.type === 'time')).toBe(true);
31
+ });
32
+
33
+ it('should parse remind intent', () => {
34
+ const result = parser.parse('Remind me to call the doctor tomorrow');
35
+ expect(result.type).toBe('remind');
36
+ expect(result.confidence).toBeGreaterThan(0);
37
+ });
38
+
39
+ it('should parse delete file intent', () => {
40
+ const result = parser.parse('Delete the file /tmp/test.txt');
41
+ expect(result.type).toBe('delete_file');
42
+ expect(result.entities.some((e) => e.type === 'file_path')).toBe(true);
43
+ });
44
+
45
+ it('should parse run command intent', () => {
46
+ const result = parser.parse('Run the terminal command to list files');
47
+ expect(result.type).toBe('run_command');
48
+ });
49
+
50
+ it('should parse summarize intent', () => {
51
+ const result = parser.parse('Summarize this article for me');
52
+ expect(result.type).toBe('summarize');
53
+ });
54
+
55
+ it('should return unknown for ambiguous input', () => {
56
+ const result = parser.parse('hello');
57
+ expect(result.type).toBe('unknown');
58
+ expect(result.confidence).toBe(0);
59
+ });
60
+
61
+ it('should detect connectors', () => {
62
+ const result = parser.parse('Send a message on Slack');
63
+ expect(result.requiredConnectors).toContain('slack');
64
+ });
65
+
66
+ it('should detect email entities', () => {
67
+ const result = parser.parse('Send an email to user@example.com');
68
+ expect(result.entities.some((e) => e.type === 'email')).toBe(true);
69
+ });
70
+
71
+ it('should preserve raw text', () => {
72
+ const text = 'Schedule a meeting for tomorrow';
73
+ const result = parser.parse(text);
74
+ expect(result.rawText).toBe(text);
75
+ });
76
+
77
+ it('should start with empty action steps', () => {
78
+ const result = parser.parse('Search for something');
79
+ expect(result.actionSteps).toEqual([]);
80
+ });
81
+
82
+ it('should detect github connector', () => {
83
+ const result = parser.parse('Create a pull request on github');
84
+ expect(result.requiredConnectors).toContain('github');
85
+ });
86
+
87
+ it('should parse query intent', () => {
88
+ const result = parser.parse('What is the capital of France?');
89
+ expect(result.type).toBe('query');
90
+ });
91
+
92
+ it('should parse translate intent', () => {
93
+ const result = parser.parse('Translate this to Spanish');
94
+ expect(result.type).toBe('translate');
95
+ });
96
+ });
@@ -0,0 +1,87 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { IntentParser } from '../src/parser.js';
3
+ import { ActionPlanner } from '../src/planner.js';
4
+
5
+ describe('ActionPlanner', () => {
6
+ const parser = new IntentParser();
7
+ const planner = new ActionPlanner();
8
+
9
+ it('should plan send_message action', () => {
10
+ const intent = parser.parse('Send a message to @john');
11
+ const steps = planner.planActions(intent);
12
+ expect(steps).toHaveLength(1);
13
+ expect(steps[0].action).toBe('send_message');
14
+ expect(steps[0].domain).toBe('messaging');
15
+ });
16
+
17
+ it('should plan edit_file with two steps (read then edit)', () => {
18
+ const intent = parser.parse('Edit the file /tmp/test.txt');
19
+ const steps = planner.planActions(intent);
20
+ expect(steps).toHaveLength(2);
21
+ expect(steps[0].action).toBe('read_file');
22
+ expect(steps[1].action).toBe('edit_file');
23
+ expect(steps[1].dependsOn).toContain(steps[0].id);
24
+ });
25
+
26
+ it('should plan browse_web action with URL', () => {
27
+ const intent = parser.parse('Navigate to https://example.com');
28
+ const steps = planner.planActions(intent);
29
+ expect(steps).toHaveLength(1);
30
+ expect(steps[0].action).toBe('navigate');
31
+ expect(steps[0].domain).toBe('web');
32
+ expect(steps[0].params.url).toBe('https://example.com');
33
+ });
34
+
35
+ it('should plan schedule action', () => {
36
+ const intent = parser.parse('Schedule a meeting for tomorrow at 3:00 pm');
37
+ const steps = planner.planActions(intent);
38
+ expect(steps).toHaveLength(1);
39
+ expect(steps[0].action).toBe('create_event');
40
+ expect(steps[0].domain).toBe('calendar');
41
+ });
42
+
43
+ it('should plan delete_file action', () => {
44
+ const intent = parser.parse('Delete /tmp/test.txt');
45
+ const steps = planner.planActions(intent);
46
+ expect(steps).toHaveLength(1);
47
+ expect(steps[0].action).toBe('delete_file');
48
+ expect(steps[0].params.path).toBe('/tmp/test.txt');
49
+ });
50
+
51
+ it('should plan search action', () => {
52
+ const intent = parser.parse('Search for TypeScript tutorials');
53
+ const steps = planner.planActions(intent);
54
+ expect(steps).toHaveLength(1);
55
+ expect(steps[0].action).toBe('web_search');
56
+ expect(steps[0].domain).toBe('web');
57
+ });
58
+
59
+ it('should plan remind action', () => {
60
+ const intent = parser.parse('Remind me tomorrow to call the doctor');
61
+ const steps = planner.planActions(intent);
62
+ expect(steps).toHaveLength(1);
63
+ expect(steps[0].action).toBe('create_reminder');
64
+ expect(steps[0].domain).toBe('calendar');
65
+ });
66
+
67
+ it('should plan summarize action', () => {
68
+ const intent = parser.parse('Summarize the article');
69
+ const steps = planner.planActions(intent);
70
+ expect(steps).toHaveLength(1);
71
+ expect(steps[0].action).toBe('summarize');
72
+ });
73
+
74
+ it('should assign unique IDs to steps', () => {
75
+ const intent = parser.parse('Edit the file /tmp/test.txt');
76
+ const steps = planner.planActions(intent);
77
+ const ids = new Set(steps.map((s) => s.id));
78
+ expect(ids.size).toBe(steps.length);
79
+ });
80
+
81
+ it('should handle unknown intent', () => {
82
+ const intent = parser.parse('hello');
83
+ const steps = planner.planActions(intent);
84
+ expect(steps).toHaveLength(1);
85
+ expect(steps[0].action).toBe('unknown');
86
+ });
87
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src/**/*"]
8
+ }