@dot-agent/language-server 0.2.0 → 0.4.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/.github/workflows/publish.yml +28 -0
- package/AGENTS.md +2 -2
- package/README.md +1 -1
- package/features/completions.js +6 -6
- package/features/definition.js +1 -1
- package/features/diagnostics.js +17 -42
- package/features/formatting.js +9 -4
- package/features/hover.js +14 -12
- package/features/links.js +2 -6
- package/features/references.js +1 -6
- package/features/rename.js +1 -6
- package/features/symbols.js +1 -1
- package/package.json +11 -5
- package/parser.js +6 -6
- package/server.js +1 -1
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: Publish to npm
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
id-token: write
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
publish:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- name: Checkout repository
|
|
16
|
+
uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Setup Node.js
|
|
19
|
+
uses: actions/setup-node@v4
|
|
20
|
+
with:
|
|
21
|
+
node-version: '24.x'
|
|
22
|
+
registry-url: 'https://registry.npmjs.org'
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: npm ci
|
|
26
|
+
|
|
27
|
+
- name: Publish package to npm
|
|
28
|
+
run: npm publish
|
package/AGENTS.md
CHANGED
|
@@ -20,7 +20,7 @@ All structural analysis is performed on **tree-sitter ASTs** (not regex). `parse
|
|
|
20
20
|
| `parser.js` | Tree-sitter engine — WASM initialization, per-document AST cache, and shared traversal helpers |
|
|
21
21
|
| `features/hover.js` | Hover documentation for all DSL keywords (static lookup, no tree traversal) |
|
|
22
22
|
| `features/completions.js` | Context-aware completions using `getContextNode` and `nodesOfType` for name lookups |
|
|
23
|
-
| `features/diagnostics.js` | Linting — dangling transitions, dead-end interact (AST),
|
|
23
|
+
| `features/diagnostics.js` | Linting — dangling transitions, dead-end interact (AST), undeclared types (AST) |
|
|
24
24
|
| `features/definition.js` | Go-to-definition via `state_decl` / `type_decl` node lookup |
|
|
25
25
|
| `features/references.js` | Find all references via `transition_stmt`, `intent_trigger`, `type_ref` traversal |
|
|
26
26
|
| `features/rename.js` | Rename symbol — same traversal as references, produces `TextEdit[]` |
|
|
@@ -153,4 +153,4 @@ The Apache 2.0 header:
|
|
|
153
153
|
| Agent grammar (canonical) | [tree-sitter-agent/grammar.js](https://github.com/daniloborges/tree-sitter-agent/blob/main/grammar.js) |
|
|
154
154
|
| Flow grammar (canonical) | [tree-sitter-agent/flow/grammar.js](https://github.com/daniloborges/tree-sitter-agent/blob/main/flow/grammar.js) |
|
|
155
155
|
| VS Code extension | [vscode-dot-agent](https://github.com/daniloborges/vscode-dot-agent) |
|
|
156
|
-
| WASM execution engine | [dot-agent-kernel](https://github.com/
|
|
156
|
+
| WASM execution engine | [dot-agent-kernel](https://github.com/dot-agent-spec/kernel-dsl) |
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ A standalone [Language Server Protocol (LSP)](https://microsoft.github.io/langua
|
|
|
8
8
|
|---|---|---|
|
|
9
9
|
| **Hover** | Keyword documentation | Keyword documentation |
|
|
10
10
|
| **Completion** | Manifest keywords, custom types | Keywords, state names, memory domains (`context.`, `session.`, …) |
|
|
11
|
-
| **Diagnostics** |
|
|
11
|
+
| **Diagnostics** | Strict block lint, undeclared types | Dangling `transition` targets, dead-end `interact` |
|
|
12
12
|
| **Go-to-Definition** | Type name → `type` declaration | `transition to stateName` → `state` declaration |
|
|
13
13
|
| **Find References** | All uses of a type | All `transition` references to a state |
|
|
14
14
|
| **Rename** | Type and its references | State and all `transition` references |
|
package/features/completions.js
CHANGED
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
const { CompletionItemKind } = require('vscode-languageserver');
|
|
20
20
|
const { nodesOfType, positionToOffset, getContextNode } = require('../parser');
|
|
21
21
|
|
|
22
|
-
const BEHAVIOR_TOP_KW = ['state', 'merge', 'on event', 'on intent', 'on offtopic'
|
|
23
|
-
const BEHAVIOR_BLOCK_KW = ['guide', 'teach', 'goal', 'interact', 'run', 'transition', 'set', 'if', 'else', 'after', 'parallel', 'apply', 'remove', 'on intent', 'on offtopic', 'on
|
|
24
|
-
const
|
|
22
|
+
const BEHAVIOR_TOP_KW = ['state', 'merge', 'on event', 'on intent', 'on offtopic'];
|
|
23
|
+
const BEHAVIOR_BLOCK_KW = ['guide', 'teach', 'goal', 'interact', 'run', 'transition', 'set', 'if', 'else', 'end', 'after', 'parallel', 'apply', 'remove', 'on intent', 'on offtopic', 'on failure', 'on success'];
|
|
24
|
+
const DESCRIPTION_TOP_KW = ['agent', 'domain', 'license', 'terms', 'privacy', 'description', 'behavior', 'requires', 'input', 'capabilities', 'output', 'type', 'concept'];
|
|
25
25
|
const STRICT_BLOCKS = new Set(['input_block', 'output_block', 'requires_block', 'capabilities_block']);
|
|
26
26
|
|
|
27
27
|
function kw(label) {
|
|
@@ -59,7 +59,7 @@ function provideCompletions(langId, tree, text, position) {
|
|
|
59
59
|
return ['script', 'subagent', 'tool'].map(kw);
|
|
60
60
|
}
|
|
61
61
|
if (/\bon\s+\S*$/.test(before)) {
|
|
62
|
-
return ['event', 'intent', 'offtopic', '
|
|
62
|
+
return ['event', 'intent', 'offtopic', 'failure', 'success'].map(kw);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
// Context-aware: top-level vs. inside a block
|
|
@@ -72,9 +72,9 @@ function provideCompletions(langId, tree, text, position) {
|
|
|
72
72
|
return (/^\s/.test(line) ? BEHAVIOR_BLOCK_KW : BEHAVIOR_TOP_KW).map(kw);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
if (langId === '
|
|
75
|
+
if (langId === 'description') {
|
|
76
76
|
if (!/^\s/.test(line)) {
|
|
77
|
-
return
|
|
77
|
+
return DESCRIPTION_TOP_KW.map(kw);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
// Inside a strict block → suggest declared types
|
package/features/definition.js
CHANGED
|
@@ -29,7 +29,7 @@ function provideDefinition(langId, tree, text, uri, position) {
|
|
|
29
29
|
if (langId === 'behavior') {
|
|
30
30
|
targetNode = nodesOfType(tree, 'state_decl')
|
|
31
31
|
.find(n => n.childForFieldName('name')?.text === word);
|
|
32
|
-
} else if (langId === '
|
|
32
|
+
} else if (langId === 'description') {
|
|
33
33
|
if (!/^[A-Z]/.test(word) && !word.includes('.')) return null;
|
|
34
34
|
targetNode = nodesOfType(tree, 'type_decl')
|
|
35
35
|
.find(n => n.childForFieldName('name')?.text === word);
|
package/features/diagnostics.js
CHANGED
|
@@ -19,11 +19,6 @@
|
|
|
19
19
|
const { DiagnosticSeverity } = require('vscode-languageserver');
|
|
20
20
|
const { nodesOfType, nodeToRange } = require('../parser');
|
|
21
21
|
|
|
22
|
-
const DEPRECATED_AGENT_KW = new Set([
|
|
23
|
-
'do', 'server', 'endpoint', 'author', 'version', 'requirements', 'step',
|
|
24
|
-
'softwareVersion', 'applicationCategory', 'character', 'publishingPrinciples',
|
|
25
|
-
]);
|
|
26
|
-
|
|
27
22
|
const STRICT_BLOCK_TYPES = {
|
|
28
23
|
input_block: 'input',
|
|
29
24
|
output_block: 'output',
|
|
@@ -31,29 +26,9 @@ const STRICT_BLOCK_TYPES = {
|
|
|
31
26
|
capabilities_block: 'capabilities',
|
|
32
27
|
};
|
|
33
28
|
|
|
34
|
-
function
|
|
29
|
+
function diagnoseDescription(tree, text) {
|
|
35
30
|
const diagnostics = [];
|
|
36
31
|
|
|
37
|
-
// ── Deprecated keywords: line scan (no tree-sitter benefit here) ──────────
|
|
38
|
-
const lines = text.split('\n');
|
|
39
|
-
for (let i = 0; i < lines.length; i++) {
|
|
40
|
-
const raw = lines[i];
|
|
41
|
-
const stripped = raw.split('//')[0].trim();
|
|
42
|
-
if (!stripped) continue;
|
|
43
|
-
const wordM = stripped.match(/^([a-zA-Z0-9_.-]+)\b/);
|
|
44
|
-
if (!wordM) continue;
|
|
45
|
-
const word = wordM[1];
|
|
46
|
-
if (DEPRECATED_AGENT_KW.has(word)) {
|
|
47
|
-
const col = raw.indexOf(word);
|
|
48
|
-
diagnostics.push({
|
|
49
|
-
range: { start: { line: i, character: col }, end: { line: i, character: col + word.length } },
|
|
50
|
-
message: `The keyword '${word}' is deprecated or invalid in the current .agent specification.`,
|
|
51
|
-
severity: DiagnosticSeverity.Error,
|
|
52
|
-
source: 'agent-dsl',
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
32
|
// ── Strict block validation ───────────────────────────────────────────────
|
|
58
33
|
const declaredTypes = new Set(
|
|
59
34
|
nodesOfType(tree, 'type_decl')
|
|
@@ -69,7 +44,7 @@ function diagnoseAgent(tree, text) {
|
|
|
69
44
|
range: nodeToRange(errorNode),
|
|
70
45
|
message: `Syntax error in '${blockName}' block. Expected: Type or Type "Description", or compact: Type1, Type2.`,
|
|
71
46
|
severity: DiagnosticSeverity.Error,
|
|
72
|
-
source: '
|
|
47
|
+
source: 'description-dsl',
|
|
73
48
|
});
|
|
74
49
|
}
|
|
75
50
|
|
|
@@ -84,7 +59,7 @@ function diagnoseAgent(tree, text) {
|
|
|
84
59
|
range: nodeToRange(idNode),
|
|
85
60
|
message: `Type '${typeName}' is not declared in this file (assuming native or external).`,
|
|
86
61
|
severity: DiagnosticSeverity.Warning,
|
|
87
|
-
source: '
|
|
62
|
+
source: 'description-dsl',
|
|
88
63
|
});
|
|
89
64
|
}
|
|
90
65
|
}
|
|
@@ -95,10 +70,11 @@ function diagnoseAgent(tree, text) {
|
|
|
95
70
|
return diagnostics;
|
|
96
71
|
}
|
|
97
72
|
|
|
98
|
-
function
|
|
73
|
+
function diagnoseBehavior(tree) {
|
|
99
74
|
const diagnostics = [];
|
|
100
75
|
|
|
101
76
|
// ── Rule 1: Dangling transitions ──────────────────────────────────────────
|
|
77
|
+
// Collect all defined state names (from both oriented_state_body and setup_state_body)
|
|
102
78
|
const definedStates = new Set(
|
|
103
79
|
nodesOfType(tree, 'state_decl')
|
|
104
80
|
.map(n => n.childForFieldName('name')?.text)
|
|
@@ -124,27 +100,26 @@ function diagnoseFlow(tree) {
|
|
|
124
100
|
for (const n of nodesOfType(tree, 'transition_stmt')) {
|
|
125
101
|
checkTransitionTarget(n.childForFieldName('state'));
|
|
126
102
|
}
|
|
127
|
-
for (const n of nodesOfType(tree, 'intent_trigger')) {
|
|
128
|
-
// inline form: on intent "..." next <state>
|
|
129
|
-
checkTransitionTarget(n.childForFieldName('state'));
|
|
130
|
-
}
|
|
131
103
|
|
|
132
|
-
// ── Rule 2: Dead-end interact
|
|
104
|
+
// ── Rule 2: Dead-end interact (now redundant but kept for safety) ────────────
|
|
105
|
+
// With the new grammar, oriented_state_body requires repeat1(handlers),
|
|
106
|
+
// so dead-end interact is structurally impossible. But kept for edge cases.
|
|
133
107
|
for (const interactNode of nodesOfType(tree, 'interact_stmt')) {
|
|
134
108
|
let ancestor = interactNode.parent;
|
|
135
|
-
while (ancestor && ancestor.type !== 'state_decl') {
|
|
109
|
+
while (ancestor && ancestor.type !== 'oriented_state_body' && ancestor.type !== 'state_decl') {
|
|
136
110
|
ancestor = ancestor.parent;
|
|
137
111
|
}
|
|
138
112
|
if (!ancestor) continue;
|
|
139
113
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const
|
|
114
|
+
// With new grammar: oriented_state_body always has handlers after interact (repeat1)
|
|
115
|
+
// So this check should never trigger, but kept for robustness
|
|
116
|
+
const hasHandlers = ancestor.descendantsOfType('intent_handler').length +
|
|
117
|
+
ancestor.descendantsOfType('offtopic_handler').length > 0;
|
|
143
118
|
|
|
144
|
-
if (!
|
|
119
|
+
if (!hasHandlers) {
|
|
145
120
|
diagnostics.push({
|
|
146
121
|
range: nodeToRange(interactNode),
|
|
147
|
-
message: "This state calls interact but has no
|
|
122
|
+
message: "This state calls interact but has no handlers (on intent/offtopic). This will trap the agent.",
|
|
148
123
|
severity: DiagnosticSeverity.Warning,
|
|
149
124
|
source: 'behavior-dsl',
|
|
150
125
|
});
|
|
@@ -156,8 +131,8 @@ function diagnoseFlow(tree) {
|
|
|
156
131
|
|
|
157
132
|
function diagnose(langId, tree, text) {
|
|
158
133
|
if (!tree) return [];
|
|
159
|
-
if (langId === '
|
|
160
|
-
if (langId === 'behavior') return
|
|
134
|
+
if (langId === 'description') return diagnoseDescription(tree, text);
|
|
135
|
+
if (langId === 'behavior') return diagnoseBehavior(tree);
|
|
161
136
|
return [];
|
|
162
137
|
}
|
|
163
138
|
|
package/features/formatting.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
'use strict';
|
|
18
18
|
|
|
19
|
-
const BLOCK_HEADERS = /^(on\s+(intent|offtopic|
|
|
19
|
+
const BLOCK_HEADERS = /^(on\s+(intent|offtopic|failure|success)|if|else|after|parallel)\b/;
|
|
20
20
|
const TOP_LEVEL_LINE = /^(state|merge)\s|^on\s+event\b/;
|
|
21
21
|
|
|
22
22
|
function formatBehavior(text) {
|
|
@@ -39,7 +39,12 @@ function formatBehavior(text) {
|
|
|
39
39
|
if (BLOCK_HEADERS.test(trimmed)) mode = 'NESTED_BODY';
|
|
40
40
|
expectedIndent = 2;
|
|
41
41
|
} else {
|
|
42
|
-
|
|
42
|
+
if (/^end\b/.test(trimmed)) {
|
|
43
|
+
mode = 'STATE_BODY';
|
|
44
|
+
expectedIndent = 2;
|
|
45
|
+
} else {
|
|
46
|
+
expectedIndent = BLOCK_HEADERS.test(trimmed) ? 2 : 4;
|
|
47
|
+
}
|
|
43
48
|
}
|
|
44
49
|
|
|
45
50
|
const actualIndent = raw.length - raw.trimStart().length;
|
|
@@ -53,7 +58,7 @@ function formatBehavior(text) {
|
|
|
53
58
|
return edits;
|
|
54
59
|
}
|
|
55
60
|
|
|
56
|
-
function
|
|
61
|
+
function formatDescription(text) {
|
|
57
62
|
const edits = [];
|
|
58
63
|
const lines = text.split('\n');
|
|
59
64
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -73,7 +78,7 @@ function formatAgent(text) {
|
|
|
73
78
|
|
|
74
79
|
function format(langId, text) {
|
|
75
80
|
if (langId === 'behavior') return formatBehavior(text);
|
|
76
|
-
if (langId === '
|
|
81
|
+
if (langId === 'description') return formatDescription(text);
|
|
77
82
|
return [];
|
|
78
83
|
}
|
|
79
84
|
|
package/features/hover.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
const { MarkupKind } = require('vscode-languageserver');
|
|
20
20
|
|
|
21
|
-
const
|
|
21
|
+
const DESCRIPTION_DOCS = {
|
|
22
22
|
'agent': '**`agent name`**\n\nDeclares a new agent. The central node of the manifest.',
|
|
23
23
|
'domain': '**`domain url`**\n\nDeclares the canonical domain for this agent, establishing cryptographic identity and ownership.',
|
|
24
24
|
'license': '**`license type`**\n\nDeclares the license under which this agent is distributed (e.g., MIT, Copyright).',
|
|
@@ -32,30 +32,32 @@ const AGENT_DOCS = {
|
|
|
32
32
|
'output': '**`output Type`**\n\nThe data type this agent returns.',
|
|
33
33
|
'type': '**`type name`**\n\nDeclares a custom type to anchor custom typing to Wikidata or Schema.org.',
|
|
34
34
|
'concept': '**`concept url`**\n\nThe Wikidata or Schema.org concept URL this type maps to.',
|
|
35
|
-
'schema': '**`schema file.json`**\n\nA JSON schema file for this type.',
|
|
36
35
|
};
|
|
37
36
|
|
|
38
37
|
const BEHAVIOR_DOCS = {
|
|
39
38
|
'merge': '**`merge "file.behavior"`**\n\nIncludes another `.behavior` file. Must appear before any `state` or `on event` declarations (preamble-only, eager loading).',
|
|
40
39
|
'state': '**`state name`**\n\nDeclares a named state. States contain the logic that runs while the agent is in that state.',
|
|
41
|
-
'on': '**`on event|intent|offtopic|
|
|
42
|
-
'run': '**`run script|subagent|tool "target"`**\n\nExecutes a script, subagent, or tool.
|
|
40
|
+
'on': '**`on event|intent|offtopic|failure|success`**\n\nBinds a handler to a trigger. Top-level: `on event`. Inside a state: `on intent`, `on offtopic`. After `run`/`apply`/`remove`: `on failure`. Inside `parallel`: `on success` (optional), `on failure` (required).',
|
|
41
|
+
'run': '**`run script|subagent|tool "target" ["parameters"]`**\n\nExecutes a script, subagent, or tool. Optionally followed by `on failure` to handle errors.',
|
|
43
42
|
'guide': '**`guide "text"`**\n\nInjects a system-level instruction into the conversation context, shaping the agent\'s persona or approach without being visible as a reply.',
|
|
44
|
-
'teach': '**`teach "
|
|
43
|
+
'teach': '**`teach "file"`**\n\nLoads a file into the agent\'s working knowledge for the duration of this state.',
|
|
45
44
|
'goal': '**`goal "text"`**\n\nSets the agent\'s objective for this state, used by the runtime for planning and alignment checks.',
|
|
46
|
-
'interact': '**`interact
|
|
45
|
+
'interact': '**`interact`**\n\nPauses execution and waits for user input.',
|
|
47
46
|
'set': '**`set domain.var = value`**\n\nAssigns a value to a memory variable. Domains: `context`, `session`, `worksession`, `user`.',
|
|
48
47
|
'context': '**`context`** memory domain — scoped to the current agent run.',
|
|
49
48
|
'session': '**`session`** memory domain — persists for the user\'s current session.',
|
|
50
49
|
'worksession': '**`worksession`** memory domain — persists across a task-oriented work session (isolated per task).',
|
|
51
50
|
'user': '**`user`** memory domain — persists across sessions for a given user.',
|
|
52
51
|
'transition': '**`transition to state`**\n\nTransitions immediately to the named state.',
|
|
53
|
-
'if': '**`if condition`**\n\nConditional execution. Condition can use `==`, `!=`, `>`, `<`, `>=`, `<=`, `and`, `or`.',
|
|
54
|
-
'else': '**`else`**\n\nAlternative branch of an `if` statement.',
|
|
52
|
+
'if': '**`if condition`**\n\nConditional execution. Condition can use `==`, `!=`, `>`, `<`, `>=`, `<=`, `and`, `or`. Close the block with `end`.',
|
|
53
|
+
'else': '**`else`**\n\nAlternative branch of an `if` statement. Close the block with `end`.',
|
|
54
|
+
'end': '**`end`**\n\nCloses an `if`/`else` block.',
|
|
55
55
|
'after': '**`after N prompts`**\n\n[Experimental] Executes a block after N user prompts have occurred in this state.',
|
|
56
|
-
'parallel': '**`parallel`**\n\n[Experimental] Runs a block of `run` statements concurrently. Follow with `on
|
|
57
|
-
'apply': '**`apply css
|
|
58
|
-
'remove': '**`remove css
|
|
56
|
+
'parallel': '**`parallel`**\n\n[Experimental] Runs a block of `run` statements concurrently. Follow with optional `on success` and required `on failure` handlers.',
|
|
57
|
+
'apply': '**`apply css "selector"`**\n\nApplies a UI manipulation to a CSS selector. Optionally followed by `on failure`.',
|
|
58
|
+
'remove': '**`remove css "selector"`**\n\nRemoves a UI element by CSS selector. Optionally followed by `on failure`.',
|
|
59
|
+
'failure': '**`on failure`**\n\nError handler block executed when a `run`, `apply`, `remove`, or `parallel` statement fails.',
|
|
60
|
+
'success': '**`on success`**\n\nOptional success handler block inside a `parallel` statement.',
|
|
59
61
|
};
|
|
60
62
|
|
|
61
63
|
function provideHover(langId, text, position) {
|
|
@@ -70,7 +72,7 @@ function provideHover(langId, text, position) {
|
|
|
70
72
|
const word = line.slice(start, end);
|
|
71
73
|
if (!word) return null;
|
|
72
74
|
|
|
73
|
-
const docs = langId === '
|
|
75
|
+
const docs = langId === 'description' ? DESCRIPTION_DOCS : BEHAVIOR_DOCS;
|
|
74
76
|
const doc = docs[word];
|
|
75
77
|
if (!doc) return null;
|
|
76
78
|
|
package/features/links.js
CHANGED
|
@@ -41,15 +41,11 @@ function provideDocumentLinks(langId, tree, docUri) {
|
|
|
41
41
|
});
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
if (langId === '
|
|
44
|
+
if (langId === 'description') {
|
|
45
45
|
for (const node of nodesOfType(tree, 'behavior_block')) {
|
|
46
46
|
const fileNode = node.childForFieldName('file');
|
|
47
47
|
if (fileNode) addLink(fileNode, fileNode.text);
|
|
48
48
|
}
|
|
49
|
-
for (const node of nodesOfType(tree, 'schema_prop')) {
|
|
50
|
-
const fileNode = node.childForFieldName('file');
|
|
51
|
-
if (fileNode) addLink(fileNode, fileNode.text);
|
|
52
|
-
}
|
|
53
49
|
}
|
|
54
50
|
|
|
55
51
|
if (langId === 'behavior') {
|
|
@@ -58,7 +54,7 @@ function provideDocumentLinks(langId, tree, docUri) {
|
|
|
58
54
|
if (pathNode) addLink(pathNode, pathNode.text);
|
|
59
55
|
}
|
|
60
56
|
for (const node of nodesOfType(tree, 'run_stmt')) {
|
|
61
|
-
const runTypeNode = node.childForFieldName('
|
|
57
|
+
const runTypeNode = node.childForFieldName('type');
|
|
62
58
|
if (runTypeNode?.text !== 'script') continue;
|
|
63
59
|
const targetNode = node.childForFieldName('target');
|
|
64
60
|
if (targetNode) addLink(targetNode, targetNode.text);
|
package/features/references.js
CHANGED
|
@@ -41,12 +41,7 @@ function provideReferences(langId, tree, text, uri, position) {
|
|
|
41
41
|
const stateNode = n.childForFieldName('state');
|
|
42
42
|
if (stateNode?.text === word) add(stateNode);
|
|
43
43
|
}
|
|
44
|
-
|
|
45
|
-
for (const n of nodesOfType(tree, 'intent_trigger')) {
|
|
46
|
-
const stateNode = n.childForFieldName('state');
|
|
47
|
-
if (stateNode?.text === word) add(stateNode);
|
|
48
|
-
}
|
|
49
|
-
} else if (langId === 'agent') {
|
|
44
|
+
} else if (langId === 'description') {
|
|
50
45
|
// Declaration
|
|
51
46
|
for (const n of nodesOfType(tree, 'type_decl')) {
|
|
52
47
|
const nameNode = n.childForFieldName('name');
|
package/features/rename.js
CHANGED
|
@@ -41,12 +41,7 @@ function provideRenameEdits(langId, tree, text, uri, position, newName) {
|
|
|
41
41
|
const stateNode = n.childForFieldName('state');
|
|
42
42
|
if (stateNode?.text === oldName) addEdit(stateNode);
|
|
43
43
|
}
|
|
44
|
-
|
|
45
|
-
for (const n of nodesOfType(tree, 'intent_trigger')) {
|
|
46
|
-
const stateNode = n.childForFieldName('state');
|
|
47
|
-
if (stateNode?.text === oldName) addEdit(stateNode);
|
|
48
|
-
}
|
|
49
|
-
} else if (langId === 'agent') {
|
|
44
|
+
} else if (langId === 'description') {
|
|
50
45
|
// Rename type declarations
|
|
51
46
|
for (const n of nodesOfType(tree, 'type_decl')) {
|
|
52
47
|
const nameNode = n.childForFieldName('name');
|
package/features/symbols.js
CHANGED
|
@@ -49,7 +49,7 @@ function provideDocumentSymbols(langId, tree) {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
if (langId === '
|
|
52
|
+
if (langId === 'description') {
|
|
53
53
|
for (const node of nodesOfType(tree, 'agent_decl')) {
|
|
54
54
|
const nameNode = node.childForFieldName('name');
|
|
55
55
|
if (!nameNode) continue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dot-agent/language-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "LSP server for .agent DSL files",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Danilo Borges <contato@daniloborg.es>",
|
|
@@ -12,7 +12,13 @@
|
|
|
12
12
|
"bugs": {
|
|
13
13
|
"url": "https://github.com/dot-agent-spec/language-server/issues"
|
|
14
14
|
},
|
|
15
|
-
"keywords": [
|
|
15
|
+
"keywords": [
|
|
16
|
+
"lsp",
|
|
17
|
+
"language-server",
|
|
18
|
+
"agent",
|
|
19
|
+
"dsl",
|
|
20
|
+
"flow"
|
|
21
|
+
],
|
|
16
22
|
"main": "server.js",
|
|
17
23
|
"engines": {
|
|
18
24
|
"node": ">=18"
|
|
@@ -21,10 +27,10 @@
|
|
|
21
27
|
"start": "node server.js --stdio"
|
|
22
28
|
},
|
|
23
29
|
"dependencies": {
|
|
24
|
-
"
|
|
30
|
+
"@dot-agent/tree-sitter": "^0.3.4",
|
|
31
|
+
"vscode-languageserver": "^10.0.0",
|
|
25
32
|
"vscode-languageserver-textdocument": "^1.0.12",
|
|
26
|
-
"web-tree-sitter": "^0.
|
|
27
|
-
"@dot-agent/tree-sitter": "^0.2.0"
|
|
33
|
+
"web-tree-sitter": "^0.26.9"
|
|
28
34
|
},
|
|
29
35
|
"publishConfig": {
|
|
30
36
|
"access": "public"
|
package/parser.js
CHANGED
|
@@ -21,17 +21,17 @@ const { Parser, Language } = require('web-tree-sitter');
|
|
|
21
21
|
const grammar = require('@dot-agent/tree-sitter');
|
|
22
22
|
|
|
23
23
|
// path.resolve ensures absolute paths survive vsix packaging and cwd changes
|
|
24
|
-
const
|
|
24
|
+
const DESCRIPTION_WASM = path.resolve(grammar.agentWasmPath);
|
|
25
25
|
const BEHAVIOR_WASM = path.resolve(grammar.behaviorWasmPath);
|
|
26
26
|
|
|
27
|
-
let
|
|
27
|
+
let descriptionParser, behaviorParser;
|
|
28
28
|
|
|
29
29
|
async function initParsers() {
|
|
30
30
|
await Parser.init();
|
|
31
|
-
const
|
|
31
|
+
const DescriptionLang = await Language.load(DESCRIPTION_WASM);
|
|
32
32
|
const Behavior = await Language.load(BEHAVIOR_WASM);
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
descriptionParser = new Parser();
|
|
34
|
+
descriptionParser.setLanguage(DescriptionLang);
|
|
35
35
|
behaviorParser = new Parser();
|
|
36
36
|
behaviorParser.setLanguage(Behavior);
|
|
37
37
|
}
|
|
@@ -42,7 +42,7 @@ const cache = new Map();
|
|
|
42
42
|
function parse(uri, langId, text, version) {
|
|
43
43
|
const prev = cache.get(uri);
|
|
44
44
|
if (prev?.version === version) return prev.tree;
|
|
45
|
-
const parser = langId === 'behavior' ? behaviorParser :
|
|
45
|
+
const parser = langId === 'behavior' ? behaviorParser : descriptionParser;
|
|
46
46
|
if (!parser) return null;
|
|
47
47
|
const tree = parser.parse(text, prev?.tree); // incremental reuse when possible
|
|
48
48
|
cache.set(uri, { version, tree });
|
package/server.js
CHANGED
|
@@ -71,7 +71,7 @@ function getTree(doc) {
|
|
|
71
71
|
|
|
72
72
|
function validate(doc) {
|
|
73
73
|
const langId = doc.languageId;
|
|
74
|
-
if (langId !== '
|
|
74
|
+
if (langId !== 'description' && langId !== 'behavior') return;
|
|
75
75
|
const tree = getTree(doc);
|
|
76
76
|
const diagnostics = diagnose(langId, tree, doc.getText());
|
|
77
77
|
connection.sendDiagnostics({ uri: doc.uri, diagnostics });
|