Skip to content

Commit b707537

Browse files
authored
Merge branch 'master' into discover-github-copilot-model-capabilities
2 parents beab82e + 1407bf5 commit b707537

5 files changed

Lines changed: 112 additions & 26 deletions

File tree

.github/workflows/release.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,18 @@ jobs:
202202
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
203203
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
204204
steps:
205+
- name: Ensure signing secrets are set
206+
if: github.repository == 'editor-code-assistant/eca'
207+
run: |
208+
if [ -z "$CSC_LINK" ]; then
209+
echo "::error::CSC_LINK secret is empty, binary would be released unsigned"
210+
exit 1
211+
fi
212+
if [ -z "$APPLE_ID" ]; then
213+
echo "::error::APPLE_ID secret is empty, binary would be released without notarization"
214+
exit 1
215+
fi
216+
205217
- uses: actions/checkout@v4
206218
- uses: actions/download-artifact@v4
207219
with:
@@ -294,6 +306,18 @@ jobs:
294306
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
295307
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
296308
steps:
309+
- name: Ensure signing secrets are set
310+
if: github.repository == 'editor-code-assistant/eca'
311+
run: |
312+
if [ -z "$CSC_LINK" ]; then
313+
echo "::error::CSC_LINK secret is empty, binary would be released unsigned"
314+
exit 1
315+
fi
316+
if [ -z "$APPLE_ID" ]; then
317+
echo "::error::APPLE_ID secret is empty, binary would be released without notarization"
318+
exit 1
319+
fi
320+
297321
- uses: actions/checkout@v4
298322
- uses: actions/download-artifact@v4
299323
with:

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@
33
## Unreleased
44

55
- Discover GitHub Copilot model APIs and reasoning variants from its authenticated `/models` endpoint.
6+
7+
## 0.145.1
8+
9+
- Notify in chat when instructions change mid-chat (prompt, contexts, rules, skills, tools), invalidating the prompt cache.
10+
11+
## 0.145.0
12+
613
- Add built-in variants for `gpt-5.6` models (`gpt-5.6-luna`, `gpt-5.6-terra`, `gpt-5.6-sol`): `none`, `low`, `medium`, `high`, `xhigh`, `max`.
714
- Support provider-level `extraHeaders`, sent on completion and models list requests. #517
815
- Improve `/init` prompt, `AGENTS.md`, and development docs.

resources/ECA_VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.144.0
1+
0.145.1

src/eca/features/chat.clj

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -67,27 +67,41 @@
6767
(when (not-empty agent) (str "/" agent))))
6868

6969
(defn ^:private static-prompt-cache-signature
70-
"SHA-256 cache identity for static prompt reuse."
70+
"Per-component SHA-256 cache identity for static prompt reuse.
71+
Returns a map of category -> hash so mid-chat instruction changes
72+
can be attributed to what changed (prompt, contexts, rules, skills, tools)."
7173
[refined-contexts static-rules path-scoped-rules skills agent config chat-id all-tools db]
72-
(let [static-contexts (vec (filter f.prompt/static-prompt-context? refined-contexts))]
73-
(digest/sha-256-hex
74-
(pr-str
75-
{:agent agent
76-
:chat-prompt-template (f.prompt/eca-chat-prompt agent config chat-id db)
77-
:workspace-roots (mapv (comp shared/uri->filename :uri) (:workspace-folders db))
78-
:environment {:os-name (str (System/getProperty "os.name") " " (System/getProperty "os.version"))
79-
:shell (or (System/getenv "SHELL") (System/getenv "ComSpec"))
80-
:user-name (System/getProperty "user.name")
81-
:home-dir (cache/user-home)}
82-
:is-subagent (boolean (get-in db [:chats chat-id :subagent]))
83-
:startup-context (get-in db [:chats chat-id :startup-context])
84-
:static-contexts static-contexts
85-
:repo-map (when (some #(= :repoMap (:type %)) static-contexts)
86-
:present)
87-
:static-rules (mapv #(select-keys % [:id :name :scope :content]) static-rules)
88-
:path-scoped-rules (mapv #(select-keys % [:id :name :scope :workspace-root :paths :enforce]) path-scoped-rules)
89-
:skills (mapv #(select-keys % [:name :description]) skills)
90-
:tools (sort (map :full-name all-tools))}))))
74+
(let [static-contexts (vec (filter f.prompt/static-prompt-context? refined-contexts))
75+
sha (comp digest/sha-256-hex pr-str)]
76+
{:prompt (sha {:agent agent
77+
:chat-prompt-template (f.prompt/eca-chat-prompt agent config chat-id db)
78+
:environment {:os-name (str (System/getProperty "os.name") " " (System/getProperty "os.version"))
79+
:shell (or (System/getenv "SHELL") (System/getenv "ComSpec"))
80+
:user-name (System/getProperty "user.name")
81+
:home-dir (cache/user-home)}
82+
:is-subagent (boolean (get-in db [:chats chat-id :subagent]))
83+
:startup-context (get-in db [:chats chat-id :startup-context])})
84+
:contexts (sha {:workspace-roots (mapv (comp shared/uri->filename :uri) (:workspace-folders db))
85+
:static-contexts static-contexts
86+
:repo-map (when (some #(= :repoMap (:type %)) static-contexts)
87+
:present)})
88+
:rules (sha {:static-rules (mapv #(select-keys % [:id :name :scope :content]) static-rules)
89+
:path-scoped-rules (mapv #(select-keys % [:id :name :scope :workspace-root :paths :enforce]) path-scoped-rules)})
90+
:skills (sha (mapv #(select-keys % [:name :description]) skills))
91+
:tools (sha (sort (map :full-name all-tools)))}))
92+
93+
(defn ^:private changed-instruction-categories
94+
"Names of instruction categories that changed vs the cached signature.
95+
Empty when the old signature has an unknown (legacy) shape."
96+
[prompt-cache static-signature agent full-model]
97+
(let [old-sig (:static-signature prompt-cache)]
98+
(cond-> []
99+
(not= (:agent prompt-cache) agent) (conj "agent")
100+
(not= (:model prompt-cache) full-model) (conj "model")
101+
(map? old-sig) (into (keep (fn [[k v]]
102+
(when (not= v (get old-sig k))
103+
(name k)))
104+
static-signature)))))
91105

92106
(defn ^:private prune-tool-results!
93107
"Prunes old tool result content from chat history to reduce context size.
@@ -1491,6 +1505,15 @@
14911505
(let [result (f.prompt/build-chat-instructions
14921506
refined-contexts static-rules path-scoped-rules skills repo-map*
14931507
agent config chat-id all-tools db)]
1508+
(when prompt-cache
1509+
(let [categories (changed-instruction-categories prompt-cache static-signature agent full-model)]
1510+
(lifecycle/send-content!
1511+
base-chat-ctx :system
1512+
{:type :text
1513+
:text (str "Instructions changed"
1514+
(when (seq categories)
1515+
(str " (" (string/join ", " categories) ")"))
1516+
", prompt cache invalidated.\n")})))
14941517
(swap! db* assoc-in [:chats chat-id :prompt-cache]
14951518
{:static (:static result)
14961519
:static-signature static-signature

test/eca/features/chat_test.clj

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
[eca.features.context :as f.context]
1111
[eca.features.index :as f.index]
1212
[eca.features.prompt :as f.prompt]
13+
[eca.features.skills :as f.skills]
1314
[eca.features.tools :as f.tools]
1415
[eca.features.tools.mcp :as f.mcp]
1516
[eca.llm-api :as llm-api]
@@ -1501,6 +1502,32 @@
15011502
(finally
15021503
(fs/delete-if-exists prompt-file))))))
15031504

1505+
(deftest instructions-changed-notice-test
1506+
(testing "System notice is sent when instructions change mid-chat"
1507+
(h/reset-components!)
1508+
(let [skills* (atom [])
1509+
mocks {:all-tools-mock (constantly [])
1510+
:api-mock (fn [{:keys [on-message-received]}]
1511+
(on-message-received {:type :finish}))}
1512+
notices (fn []
1513+
(->> (h/messages)
1514+
:chat-content-received
1515+
(filter #(= :system (:role %)))
1516+
(keep #(get-in % [:content :text]))
1517+
(filter #(string/includes? % "Instructions changed"))))]
1518+
(with-redefs [f.skills/all (fn [& _] @skills*)]
1519+
(let [{:keys [chat-id]} (prompt! {:message "Hi"} mocks)]
1520+
(is (empty? (notices)) "No notice on the first prompt")
1521+
(h/reset-messenger!)
1522+
(prompt! {:message "Again" :chat-id chat-id} mocks)
1523+
(is (empty? (notices)) "No notice when nothing changed")
1524+
(h/reset-messenger!)
1525+
(reset! skills* [{:name "my-skill" :description "Does things"}])
1526+
(prompt! {:message "Changed" :chat-id chat-id} mocks)
1527+
(is (= ["Instructions changed (skills), prompt cache invalidated.\n"]
1528+
(notices))
1529+
"Notice names the changed category"))))))
1530+
15041531
(deftest prompt-cache-key-includes-agent-test
15051532
(testing "sync-or-async-prompt! receives prompt-cache-key scoped by active agent"
15061533
(h/reset-components!)
@@ -1808,11 +1835,16 @@
18081835
:agent "plan"
18091836
:model "openai/gpt-4.1"})
18101837
(is (= 3 @build-calls*) "Changing model should rebuild instructions")
1811-
(is (match? {:static "static-3"
1812-
:static-signature (m/pred #(re-matches #"[0-9a-f]{64}" %))
1813-
:agent "plan"
1814-
:model "openai/gpt-4.1"}
1815-
(get-in (h/db) [:chats chat-id :prompt-cache]))))))
1838+
(let [sha? (m/pred #(re-matches #"[0-9a-f]{64}" %))]
1839+
(is (match? {:static "static-3"
1840+
:static-signature {:prompt sha?
1841+
:contexts sha?
1842+
:rules sha?
1843+
:skills sha?
1844+
:tools sha?}
1845+
:agent "plan"
1846+
:model "openai/gpt-4.1"}
1847+
(get-in (h/db) [:chats chat-id :prompt-cache])))))))
18161848

18171849
(deftest message-content->chat-content-image-test
18181850
(testing "image_generation_call role replays as a single :image ChatContent under assistant"

0 commit comments

Comments
 (0)