Skip to content

Commit 4bdc412

Browse files
authored
Merge pull request #110 from editor-code-assistant/feature/custom-behavior
Feature/custom behavior
2 parents 0208572 + 4c1546b commit 4bdc412

18 files changed

Lines changed: 499 additions & 219 deletions

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44

55
- Added missing parameters to `toolCallRejected` where possible. PR #109
66
- Improve plan prompt present plan step.
7+
- Add custom behavior configuration support. #79
8+
- Behaviors can now define `defaultModel`, `disabledTools`, `systemPromptFile`, and `toolCall` approval rules.
9+
- Built-in `agent` and `plan` behaviors are pre-configured.
10+
- Replace `systemPromptTemplateFile` with `systemPromptFile` for complete prompt files instead of templates.
11+
- Remove `nativeTools` configuration in favor of `toolCall` approval and `disabledTools`.
12+
- Native tools are now always enabled by default, controlled via `disabledTools` and `toolCall` approval.
713

814
## 0.49.0
915

docs/configuration.md

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -341,13 +341,19 @@ There are 3 possible ways to configure rules following this order of priority:
341341
defaultModel?: string;
342342
rules?: [{path: string;}];
343343
commands?: [{path: string;}];
344-
systemPromptTemplateFile?: string;
345-
nativeTools?: {
346-
filesystem: {enabled: boolean};
347-
shell: {enabled: boolean,
348-
excludeCommands: string[]};
349-
editor: {enabled: boolean,};
350-
};
344+
behavior?: {[key: string]: {
345+
systemPromptFile?: string;
346+
defaultModel?: string;
347+
disabledTools?: string[];
348+
toolCall?: {
349+
approval?: {
350+
byDefault?: 'ask' | 'allow' | 'deny';
351+
allow?: {[key: string]: {argsMatchers?: {[key: string]: string[]}}};
352+
ask?: {[key: string]: {argsMatchers?: {[key: string]: string[]}}};
353+
deny?: {[key: string]: {argsMatchers?: {[key: string]: string[]}}};
354+
};
355+
};
356+
}};
351357
customTools?: {[key: string]: {
352358
description: string;
353359
command: string;
@@ -404,10 +410,6 @@ There are 3 possible ways to configure rules following this order of priority:
404410
"defaultModel": null, // let ECA decides the default model.
405411
"rules" : [],
406412
"commands" : [],
407-
"nativeTools": {"filesystem": {"enabled": true},
408-
"shell": {"enabled": true,
409-
"excludeCommands": []},
410-
"editor": {"enabled": true}},
411413
"disabledTools": [],
412414
"toolCall": {
413415
"approval": {

docs/features.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ It supports both MCP server tools + ECA native tools.
2626

2727
#### Native tools
2828

29-
ECA support built-in tools to avoid user extra installation and configuration, these tools are always included on models requests that support tools and can be [disabled/configured via config](./configuration.md) `nativeTools`.
29+
ECA support built-in tools to avoid user extra installation and configuration, these tools are always included on models requests that support tools and can be [disabled via config](./configuration.md) `disabledTools`.
3030

3131
##### Filesystem
3232

@@ -44,7 +44,7 @@ Provides access to filesystem under workspace root, listing, reading and writing
4444

4545
Provides access to run shell commands, useful to run build tools, tests, and other common commands, supports exclude/include commands.
4646

47-
- `eca_shell_command`: run shell command. Supports configs to exclude commands via `:nativeTools :shell :excludeCommands`.
47+
- `eca_shell_command`: run shell command. Command exclusion can be configured using toolCall approval configuration with regex patterns.
4848

4949
##### Editor
5050

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,24 @@
1+
You are ECA (Editor Code Assistant), an AI coding assistant that operates on an editor.
2+
3+
You are pair programming with a USER to solve their coding task. Each time the USER sends a message, we may automatically attach some context information about their current state, such as passed contexts, rules defined by USER, project structure, and more. This information may or may not be relevant to the coding task, it is up for you to decide.
4+
15
You are an agent - please keep going until the user's query is completely resolved, before ending your turn and yielding back to the user. Only terminate your turn when you are sure that the problem is solved. Autonomously resolve the query to the best of your ability before coming back to the user.
26

37
<edit_file_instructions>
48
NEVER show the code edits or new files to the user - only call the proper tool. The system will apply and display the edits.
59
For each file, give a short description of what needs to be edited, then use the available tool. You can use the tool multiple times in a response, and you can keep writing text after using a tool. Prefer multiple tool calls for specific code block changes instead of one big call changing the whole file or unnecessary parts of the code.
610
</edit_file_instructions>
11+
12+
<communication>
13+
The chat is markdown mode.
14+
When using markdown in assistant messages, use backticks to format file, directory, function, and class names.
15+
Pay attention to the language name after the code block backticks start, use the full language name like 'javascript' instead of 'js'.
16+
</communication>
17+
18+
<tool_calling>
19+
You have tools at your disposal to solve the coding task. Follow these rules regarding tool calls:
20+
1. ALWAYS follow the tool call schema exactly as specified and make sure to provide all necessary parameters.
21+
2. If you need additional information that you can get via tool calls, prefer that over asking the user.
22+
3. If you are not sure about file content or codebase structure pertaining to the user's request, use your tools to read files and gather the relevant information: do NOT guess or make up an answer.
23+
4. You have the capability to call multiple tools in a single response, batch your tool calls together for optimal performance.
24+
</tool_calling>

resources/prompts/plan_behavior.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
You are ECA (Editor Code Assistant), an AI coding assistant.
2+
13
## Plan Mode
24

35
You are in planning mode. Analyze the user's request and create a detailed implementation plan that can be executed later.
@@ -49,3 +51,17 @@ NEVER print codeblocks for file changes unless explicitly requested - use the ap
4951

5052
### Remember
5153
Plans can involve many activities beyond code changes. Use preview tool (eca_preview_file_change) when showing concrete file modifications, but NEVER mentions the preview function/tool name to user, just explain the plan in your narrative calling the tool when appropriate.
54+
55+
<communication>
56+
The chat is markdown mode.
57+
When using markdown in assistant messages, use backticks to format file, directory, function, and class names.
58+
Pay attention to the language name after the code block backticks start, use the full language name like 'javascript' instead of 'js'.
59+
</communication>
60+
61+
<tool_calling>
62+
You have tools at your disposal to solve the coding task. Follow these rules regarding tool calls:
63+
1. ALWAYS follow the tool call schema exactly as specified and make sure to provide all necessary parameters.
64+
2. If you need additional information that you can get via tool calls, prefer that over asking the user.
65+
3. If you are not sure about file content or codebase structure pertaining to the user's request, use your tools to read files and gather the relevant information: do NOT guess or make up an answer.
66+
4. You have the capability to call multiple tools in a single response, batch your tool calls together for optimal performance.
67+
</tool_calling>

src/eca/config.clj

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,22 @@
5858
"claude-sonnet-4" {}}}
5959
"ollama" {:url "http://localhost:11434"
6060
:urlEnv "OLLAMA_API_URL"}}
61+
:behavior {"agent" {:systemPromptFile "prompts/agent_behavior.md"
62+
:disabledTools ["eca_preview_file_change"]}
63+
"plan" {:systemPromptFile "prompts/plan_behavior.md"
64+
:disabledTools ["eca_edit_file" "eca_write_file" "eca_move_file"]
65+
:toolCall {:approval {:deny {"eca_shell_command"
66+
{:argsMatchers {"command" [".*>.*",
67+
".*\\|\\s*(tee|dd|xargs).*",
68+
".*\\b(sed|awk|perl)\\s+.*-i.*",
69+
".*\\b(rm|mv|cp|touch|mkdir)\\b.*",
70+
".*git\\s+(add|commit|push).*",
71+
".*npm\\s+install.*",
72+
".*-c\\s+[\"'].*open.*[\"']w[\"'].*",
73+
".*bash.*-c.*>.*"]}}}}}}}
6174
:defaultModel nil
6275
:rules []
6376
:commands []
64-
:nativeTools {:filesystem {:enabled true}
65-
:shell {:enabled true
66-
:excludeCommands []}
67-
:editor {:enabled true}}
6877
:disabledTools []
6978
:toolCall {:approval {:byDefault "ask"
7079
:allow {"eca_preview_file_change" {}
@@ -83,6 +92,18 @@
8392
:repoMap {:maxTotalEntries 800
8493
:maxEntriesPerDir 50}}})
8594

95+
(def ^:private fallback-behavior "agent")
96+
97+
(defn validate-behavior-name
98+
"Validates if a behavior exists in config. Returns the behavior if valid,
99+
or the fallback behavior if not."
100+
[behavior config]
101+
(if (contains? (:behavior config) behavior)
102+
behavior
103+
(do (logger/warn logger-tag (format "Unknown behavior '%s' specified, falling back to '%s'"
104+
behavior fallback-behavior))
105+
fallback-behavior)))
106+
86107
(defn get-env [env] (System/getenv env))
87108
(defn get-property [property] (System/getProperty property))
88109

@@ -197,7 +218,8 @@
197218
{:kebab-case
198219
[[:providers]]
199220
:stringfy
200-
[[:providers]
221+
[[:behavior]
222+
[:providers]
201223
[:providers :ANY :models]
202224
[:toolCall :approval :allow]
203225
[:toolCall :approval :allow :ANY :argsMatchers]
@@ -207,7 +229,14 @@
207229
[:toolCall :approval :deny :ANY :argsMatchers]
208230
[:customTools]
209231
[:customTools :ANY :schema :properties]
210-
[:mcpServers]]})
232+
[:mcpServers]
233+
;; Behavior-specific toolCall
234+
[:behavior :ANY :toolCall :approval :allow]
235+
[:behavior :ANY :toolCall :approval :allow :ANY :argsMatchers]
236+
[:behavior :ANY :toolCall :approval :ask]
237+
[:behavior :ANY :toolCall :approval :ask :ANY :argsMatchers]
238+
[:behavior :ANY :toolCall :approval :deny]
239+
[:behavior :ANY :toolCall :approval :deny :ANY :argsMatchers]]})
211240

212241
(defn all [db]
213242
(let [initialization-config @initialization-config*

src/eca/db.clj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
:providers-config-hash nil
2525
:last-config-notified {}
2626
:stopping false
27-
:chat-behaviors ["agent" "plan"]
2827
:models {}
2928
:mcp-clients {}
3029

src/eca/features/chat.clj

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
[cheshire.core :as json]
44
[clojure.set :as set]
55
[clojure.string :as string]
6+
[eca.config :as config]
67
[eca.db :as db]
78
[eca.features.commands :as f.commands]
89
[eca.features.context :as f.context]
@@ -478,7 +479,7 @@
478479
details (f.tools/tool-call-details-before-invocation name arguments)
479480
summary (f.tools/tool-call-summary all-tools name arguments)
480481
origin (tool-name->origin name all-tools)
481-
approval (f.tools/approval all-tools name arguments db config)
482+
approval (f.tools/approval all-tools name arguments db config behavior)
482483
ask? (= :ask approval)]
483484
;; assert: In :preparing or :stopped
484485
;; Inform client the tool is about to run and store approval promise
@@ -656,20 +657,27 @@
656657
(swap! db* assoc-in [:chats new-id] {:id new-id})
657658
new-id))
658659
db @db*
659-
full-model (or model (default-model db config))
660+
raw-behavior (or behavior
661+
(-> config :chat :defaultBehavior) ;; legacy
662+
(-> config :defaultBehavior))
663+
selected-behavior (config/validate-behavior-name raw-behavior config)
664+
behavior-config (get-in config [:behavior selected-behavior])
665+
;; Simple model selection without behavior switching logic
666+
full-model (or model
667+
(:defaultModel behavior-config)
668+
(default-model db config))
660669
rules (f.rules/all config (:workspace-folders db))
661670
refined-contexts (f.context/raw-contexts->refined contexts db config)
662671
repo-map* (delay (f.index/repo-map db config {:as-string? true}))
663672
instructions (f.prompt/build-instructions refined-contexts
664673
rules
665674
repo-map*
666-
(or behavior
667-
(-> config :chat :defaultBehavior) ;; legacy
668-
(-> config :defaultBehavior))
675+
selected-behavior
669676
config)
670677
chat-ctx {:chat-id chat-id
671678
:contexts contexts
672-
:behavior behavior
679+
:behavior selected-behavior
680+
:behavior-config behavior-config
673681
:instructions instructions
674682
:full-model full-model
675683
:db* db*

src/eca/features/prompt.clj

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,11 @@
1212

1313
(def ^:private logger-tag "[PROMPT]")
1414

15-
(defn ^:private base-prompt-template* [] (slurp (io/resource "prompts/eca_base.md")))
16-
(def ^:private base-prompt-template (memoize base-prompt-template*))
15+
;; Built-in behavior prompts are now complete files, not templates
16+
(defn ^:private load-builtin-prompt* [filename]
17+
(slurp (io/resource (str "prompts/" filename))))
1718

18-
(defn ^:private plan-behavior* [] (slurp (io/resource "prompts/plan_behavior.md")))
19-
(def ^:private plan-behavior (memoize plan-behavior*))
20-
21-
(defn ^:private agent-behavior* [] (slurp (io/resource "prompts/agent_behavior.md")))
22-
(def ^:private agent-behavior (memoize agent-behavior*))
19+
(def ^:private load-builtin-prompt (memoize load-builtin-prompt*))
2320

2421
(defn ^:private init-prompt-template* [] (slurp (io/resource "prompts/init.md")))
2522
(def ^:private init-prompt-template (memoize init-prompt-template*))
@@ -32,13 +29,24 @@
3229
vars))
3330

3431
(defn ^:private eca-prompt [behavior config]
35-
(let [prompt (or (some-> (:systemPromptTemplateFile config) slurp)
36-
(base-prompt-template))]
37-
(replace-vars
38-
prompt
39-
{:behavior (case behavior
40-
"plan" (plan-behavior)
41-
"agent" (agent-behavior))})))
32+
(let [behavior-config (get-in config [:behavior behavior])
33+
;; Use systemPromptFile from behavior config, or fall back to built-in
34+
prompt-file (or (:systemPromptFile behavior-config)
35+
;; For built-in behaviors without explicit config
36+
(when (#{"agent" "plan"} behavior)
37+
(str "prompts/" behavior "_behavior.md")))]
38+
(cond
39+
;; Custom behavior with absolute path
40+
(and prompt-file (string/starts-with? prompt-file "/"))
41+
(slurp prompt-file)
42+
43+
;; Built-in or resource path
44+
prompt-file
45+
(load-builtin-prompt (some-> prompt-file (string/replace-first #"prompts/" "")))
46+
47+
;; Fallback for unknown behavior
48+
:else
49+
(load-builtin-prompt "agent_behavior.md"))))
4250

4351
(defn build-instructions [refined-contexts rules repo-map* behavior config]
4452
(multi-str

0 commit comments

Comments
 (0)