@agent-native/core 0.101.4 → 0.101.6
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/README.md +1 -1
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +13 -0
- package/corpus/core/docs/content/locales/ar-SA/pure-agent-apps.mdx +2 -2
- package/corpus/core/docs/content/locales/de-DE/pure-agent-apps.mdx +2 -2
- package/corpus/core/docs/content/locales/es-ES/pure-agent-apps.mdx +2 -2
- package/corpus/core/docs/content/locales/fr-FR/pure-agent-apps.mdx +2 -2
- package/corpus/core/docs/content/locales/hi-IN/pure-agent-apps.mdx +2 -2
- package/corpus/core/docs/content/locales/ja-JP/pure-agent-apps.mdx +2 -2
- package/corpus/core/docs/content/locales/ko-KR/pure-agent-apps.mdx +2 -2
- package/corpus/core/docs/content/locales/pt-BR/pure-agent-apps.mdx +2 -2
- package/corpus/core/docs/content/locales/zh-CN/pure-agent-apps.mdx +2 -2
- package/corpus/core/docs/content/locales/zh-TW/pure-agent-apps.mdx +2 -2
- package/corpus/core/docs/content/pure-agent-apps.mdx +58 -14
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/action.ts +15 -0
- package/corpus/core/src/agent/production-agent.ts +215 -19
- package/corpus/core/src/coding-tools/run-code.ts +11 -5
- package/corpus/core/src/extensions/actions.ts +15 -0
- package/corpus/core/src/extensions/url-safety.ts +11 -1
- package/corpus/core/src/notifications/channels.ts +61 -30
- package/corpus/core/src/server/action-discovery.ts +4 -1
- package/corpus/core/src/server/agent-chat/context-tools.ts +4 -0
- package/corpus/templates/analytics/AGENTS.md +6 -0
- package/corpus/templates/analytics/README.md +1 -1
- package/corpus/templates/analytics/actions/data-source-status.ts +19 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +5 -1
- package/corpus/templates/analytics/app/routes/_index.tsx +2 -2
- package/corpus/templates/analytics/changelog/2026-07-14-analytics-chat-guides-you-to-connect-a-data-source-with-a-di.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-14-analytics-landing-pages-now-clearly-position-the-app-as-an-o.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-15-daily-dashboard-emails-now-include-the-complete-dashboard-sc.md +6 -0
- package/corpus/templates/analytics/server/jobs/dashboard-report.ts +8 -1
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +64 -12
- package/corpus/templates/analytics/server/lib/real-data-actions.ts +15 -3
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +364 -4
- package/corpus/templates/clips/app/components/dictate/vocabulary-section.tsx +1 -0
- package/corpus/templates/clips/app/components/library/folder-tree.tsx +12 -9
- package/corpus/templates/clips/app/components/library/library-layout.tsx +19 -16
- package/corpus/templates/clips/app/routes/_app.spaces._index.tsx +13 -9
- package/corpus/templates/clips/changelog/2026-07-15-folders-and-sidebar-links-are-clickable-again-immediately-af.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-15-hovering-a-truncated-folder-name-now-shows-the-full-name.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-15-space-creation-controls-are-now-shown-only-to-organization-a.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-15-vocabulary-terms-can-now-be-deleted-from-the-dictation-dicti.md +6 -0
- package/corpus/templates/clips/desktop/src/styles.css +23 -1
- package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +49 -7
- package/corpus/templates/clips/server/lib/recordings.ts +5 -2
- package/dist/action.d.ts +12 -0
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js +2 -0
- package/dist/action.js.map +1 -1
- package/dist/agent/production-agent.d.ts +24 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +175 -18
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/coding-tools/run-code.d.ts +5 -2
- package/dist/coding-tools/run-code.d.ts.map +1 -1
- package/dist/coding-tools/run-code.js +11 -5
- package/dist/coding-tools/run-code.js.map +1 -1
- package/dist/extensions/actions.d.ts.map +1 -1
- package/dist/extensions/actions.js +14 -0
- package/dist/extensions/actions.js.map +1 -1
- package/dist/extensions/url-safety.d.ts +6 -0
- package/dist/extensions/url-safety.d.ts.map +1 -1
- package/dist/extensions/url-safety.js +6 -0
- package/dist/extensions/url-safety.js.map +1 -1
- package/dist/notifications/channels.d.ts.map +1 -1
- package/dist/notifications/channels.js +38 -22
- package/dist/notifications/channels.js.map +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +4 -1
- package/dist/server/action-discovery.js.map +1 -1
- package/dist/server/agent-chat/context-tools.d.ts.map +1 -1
- package/dist/server/agent-chat/context-tools.js +4 -0
- package/dist/server/agent-chat/context-tools.js.map +1 -1
- package/docs/content/locales/ar-SA/pure-agent-apps.mdx +2 -2
- package/docs/content/locales/de-DE/pure-agent-apps.mdx +2 -2
- package/docs/content/locales/es-ES/pure-agent-apps.mdx +2 -2
- package/docs/content/locales/fr-FR/pure-agent-apps.mdx +2 -2
- package/docs/content/locales/hi-IN/pure-agent-apps.mdx +2 -2
- package/docs/content/locales/ja-JP/pure-agent-apps.mdx +2 -2
- package/docs/content/locales/ko-KR/pure-agent-apps.mdx +2 -2
- package/docs/content/locales/pt-BR/pure-agent-apps.mdx +2 -2
- package/docs/content/locales/zh-CN/pure-agent-apps.mdx +2 -2
- package/docs/content/locales/zh-TW/pure-agent-apps.mdx +2 -2
- package/docs/content/pure-agent-apps.mdx +58 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -91,7 +91,7 @@ Edit local Markdown/MDX files, generate rich interactive custom blocks, and draf
|
|
|
91
91
|
|
|
92
92
|
<a href="https://agent-native.com/templates/analytics"><img src="https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F4933a80cc3134d7e874631f688be828a?format=webp&width=800" alt="Analytics app" width="100%" /></a>
|
|
93
93
|
|
|
94
|
-
**
|
|
94
|
+
**Open-Source Alternative to Amplitude and FullStory**
|
|
95
95
|
|
|
96
96
|
Connect analytics data sources, prompt for real charts, and build reusable dashboards.
|
|
97
97
|
|
package/corpus/README.md
CHANGED
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @agent-native/core
|
|
2
2
|
|
|
3
|
+
## 0.101.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- dd47e0a: Clarify how to add a UI later while preserving a headless app's shared action and data contract.
|
|
8
|
+
|
|
9
|
+
## 0.101.5
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 9dd88f4: Opt the dedicated `get-code-execution` and `refresh-screen` volatile reads out of the duplicate read-only tool-call guard via the new `dedupe: false` action option while retaining default duplicate protection for normal `run-code` executions. Also raise `get-extension` and `get-extension-history-version` result caps to 500,000 and 2,000,000 characters respectively so JSON serialization overhead cannot slice mid-content and corrupt source reads for large extensions or their history.
|
|
14
|
+
- 9dd88f4: Prevent repeated read-only tool loops while preserving trimmed results, allow volatile reads to opt out of deduping, and enforce notification webhook allowlists at the scope that supplied each secret.
|
|
15
|
+
|
|
3
16
|
## 0.101.4
|
|
4
17
|
|
|
5
18
|
### Patch Changes
|
|
@@ -68,9 +68,9 @@ Automation-first app هو Agent-Native app بلا متصفح من أجل schedul
|
|
|
68
68
|
|
|
69
69
|
→ [**Agent Surfaces — Headless agent**](/docs/agent-surfaces#headless)
|
|
70
70
|
|
|
71
|
-
## أضف
|
|
71
|
+
## أضف واجهة مستخدم لاحقاً {#add-chat-later}
|
|
72
72
|
|
|
73
|
-
يختار `--headless` السقالة الأولية فقط، ولا يوجد أمر `agent-native` يحوّل مشروعاً قائماً بلا واجهة إلى تطبيق
|
|
73
|
+
يختار `--headless` السقالة الأولية فقط، ولا يوجد أمر `agent-native` يحوّل مشروعاً قائماً بلا واجهة إلى تطبيق متصفح في مكانه. عندما يحتاج الأشخاص إلى واجهة مستخدم، استخدم [قالب Chat](/docs/template-chat) كمرجع للترحيل: احتفظ بالـ actions وبيانات SQL والتعليمات والمهارات، ثم أضف shell المتصفح وserver plugins. وإذا كانت الواجهة محادثة، أضف `<AgentChatSurface>` وplugin للدردشة يحمّل actions الحالية. تحقّق من `pnpm typecheck` ومن تدفق واجهة يستدعي action قائمة. استخدم `agent-native add` لمخططات التكامل، لا لهذا التحويل.
|
|
74
74
|
|
|
75
75
|
## ما هي الخطوة التالية
|
|
76
76
|
|
|
@@ -70,9 +70,9 @@ Die Box, das Gerüst, der Repo-Zugriff und die Lauffreigabe sind jetzt an einem
|
|
|
70
70
|
|
|
71
71
|
→ [**Agent Surfaces — Headless agent**](/docs/agent-surfaces#headless)
|
|
72
72
|
|
|
73
|
-
##
|
|
73
|
+
## Später eine UI hinzufügen {#add-chat-later}
|
|
74
74
|
|
|
75
|
-
`--headless` wählt nur das anfängliche Scaffold; es gibt keinen `agent-native`-Befehl, der ein bestehendes action-only-Projekt direkt in eine
|
|
75
|
+
`--headless` wählt nur das anfängliche Scaffold; es gibt keinen `agent-native`-Befehl, der ein bestehendes action-only-Projekt direkt in eine Browser-App umwandelt. Wenn Menschen eine UI brauchen, nutze das [Chat-Template](/docs/template-chat) als Migrationsreferenz: Behalte Actions, SQL-Daten, Anweisungen und Skills bei und übernimm Browser-Shell und Server-Plugins. Für eine Chat-Oberfläche kommen `<AgentChatSurface>` und ein Chat-Plugin hinzu, das die vorhandenen Actions lädt. Prüfe `pnpm typecheck` und einen UI-Ablauf, der eine vorhandene Action aufruft. `agent-native add` ist für Integrations-Blaupausen gedacht, nicht für diese Umwandlung.
|
|
76
76
|
|
|
77
77
|
## Was kommt als nächstes
|
|
78
78
|
|
|
@@ -70,9 +70,9 @@ la caja, el andamio, el acceso al repositorio y el uso compartido de ejecuciones
|
|
|
70
70
|
|
|
71
71
|
→ [**Agent Surfaces — Headless agent**](/docs/agent-surfaces#headless)
|
|
72
72
|
|
|
73
|
-
## Añade
|
|
73
|
+
## Añade una UI más adelante {#add-chat-later}
|
|
74
74
|
|
|
75
|
-
`--headless` solo elige el scaffold inicial; no hay un comando de `agent-native` que convierta en el sitio un proyecto existente solo de acciones en una app de
|
|
75
|
+
`--headless` solo elige el scaffold inicial; no hay un comando de `agent-native` que convierta en el sitio un proyecto existente solo de acciones en una app de navegador. Cuando las personas necesiten una UI, usa la [plantilla Chat](/docs/template-chat) como referencia de migración: conserva las actions, los datos SQL, las instrucciones y las skills, y añade el shell del navegador y los plugins de servidor. Para una superficie de chat, añade `<AgentChatSurface>` y un plugin de chat que cargue las actions existentes. Comprueba `pnpm typecheck` y un flujo de UI que llame una action existente. `agent-native add` es para planos de integración, no para esta conversión.
|
|
76
76
|
|
|
77
77
|
## ¿Qué sigue?
|
|
78
78
|
|
|
@@ -68,9 +68,9 @@ la boîte, l'échafaudage, l'accès au dépôt et le partage d'exécution se tro
|
|
|
68
68
|
|
|
69
69
|
→ [**Agent Surfaces — Headless agent**](/docs/agent-surfaces#headless)
|
|
70
70
|
|
|
71
|
-
## Ajouter
|
|
71
|
+
## Ajouter une UI plus tard {#add-chat-later}
|
|
72
72
|
|
|
73
|
-
`--headless` choisit seulement le scaffold initial ; aucune commande `agent-native` ne convertit directement un projet existant fondé uniquement sur des actions en application
|
|
73
|
+
`--headless` choisit seulement le scaffold initial ; aucune commande `agent-native` ne convertit directement un projet existant fondé uniquement sur des actions en application navigateur. Quand les personnes ont besoin d'une UI, utilisez le [modèle Chat](/docs/template-chat) comme référence de migration : conservez les actions, les données SQL, les instructions et les skills, puis ajoutez le shell navigateur et les plugins serveur. Pour une surface de chat, ajoutez `<AgentChatSurface>` et un plugin de chat qui charge les actions existantes. Vérifiez `pnpm typecheck` et un flux UI appelant une action existante. `agent-native add` sert aux blueprints d'intégration, pas à cette conversion.
|
|
74
74
|
|
|
75
75
|
## Quelle est la prochaine étape
|
|
76
76
|
|
|
@@ -68,9 +68,9 @@ Automation-first app scheduled jobs, queues, scripts, integration workers और
|
|
|
68
68
|
|
|
69
69
|
→ [**Agent Surfaces — Headless agent**](/docs/agent-surfaces#headless)
|
|
70
70
|
|
|
71
|
-
## बाद में
|
|
71
|
+
## बाद में UI जोड़ें {#add-chat-later}
|
|
72
72
|
|
|
73
|
-
`--headless` केवल शुरुआती scaffold चुनता है; कोई `agent-native` command किसी मौजूदा action-only project को in-place
|
|
73
|
+
`--headless` केवल शुरुआती scaffold चुनता है; कोई `agent-native` command किसी मौजूदा action-only project को in-place browser app में नहीं बदलती। जब लोगों को UI चाहिए, [Chat template](/docs/template-chat) को migration reference की तरह उपयोग करें: actions, SQL data, instructions और skills रखें, फिर browser shell और server plugins जोड़ें। Chat surface के लिए `<AgentChatSurface>` और मौजूदा actions लोड करने वाला chat plugin भी जोड़ें। `pnpm typecheck` और एक ऐसा UI flow जाँचें जो किसी मौजूदा action को call करे। `agent-native add` integration blueprints के लिए है, इस conversion के लिए नहीं।
|
|
74
74
|
|
|
75
75
|
## आगे क्या है
|
|
76
76
|
|
|
@@ -68,9 +68,9 @@ Automation-first app は、scheduled jobs、queues、scripts、integration worke
|
|
|
68
68
|
|
|
69
69
|
→ [**Agent Surfaces — Headless agent**](/docs/agent-surfaces#headless)
|
|
70
70
|
|
|
71
|
-
##
|
|
71
|
+
## 後から UI を追加する {#add-chat-later}
|
|
72
72
|
|
|
73
|
-
`--headless` が選ぶのは初期 scaffold だけで、既存の action-only
|
|
73
|
+
`--headless` が選ぶのは初期 scaffold だけで、既存の action-only プロジェクトをその場でブラウザーアプリに変換する `agent-native` コマンドはありません。UI が必要になったら、移行の参照として [Chat template](/docs/template-chat) を使います。actions、SQL データ、instructions、skills は保持し、browser shell と server plugins を追加してください。チャット surface には `<AgentChatSurface>` と既存 actions を読み込む chat plugin も追加します。`pnpm typecheck` と既存 action を呼ぶ UI フローを確認します。`agent-native add` は integration blueprints 用であり、この変換用ではありません。
|
|
74
74
|
|
|
75
75
|
## 次は何ですか
|
|
76
76
|
|
|
@@ -66,9 +66,9 @@ Automation-first app은 scheduled jobs, queues, scripts, integration workers, ex
|
|
|
66
66
|
|
|
67
67
|
→ [**Agent Surfaces — Headless agent**](/docs/agent-surfaces#headless)
|
|
68
68
|
|
|
69
|
-
## 나중에
|
|
69
|
+
## 나중에 UI 추가하기 {#add-chat-later}
|
|
70
70
|
|
|
71
|
-
`--headless`는 초기 scaffold만 선택하며, 기존 action-only 프로젝트를 제자리에서
|
|
71
|
+
`--headless`는 초기 scaffold만 선택하며, 기존 action-only 프로젝트를 제자리에서 브라우저 앱으로 변환하는 `agent-native` 명령은 없습니다. UI가 필요해지면 [Chat 템플릿](/docs/template-chat)을 마이그레이션 기준으로 사용하세요. actions, SQL 데이터, instructions, skills를 유지하고 browser shell과 server plugins를 추가합니다. Chat surface에는 `<AgentChatSurface>`와 기존 actions를 로드하는 chat plugin도 추가합니다. `pnpm typecheck`와 기존 action을 호출하는 UI flow를 확인하세요. `agent-native add`는 이 변환이 아니라 integration blueprints용입니다.
|
|
72
72
|
|
|
73
73
|
## 다음 단계
|
|
74
74
|
|
|
@@ -68,9 +68,9 @@ a caixa, o scaffold, o acesso ao repositório e o compartilhamento de execução
|
|
|
68
68
|
|
|
69
69
|
→ [**Agent Surfaces — Headless agent**](/docs/agent-surfaces#headless)
|
|
70
70
|
|
|
71
|
-
## Adicione
|
|
71
|
+
## Adicione uma UI depois {#add-chat-later}
|
|
72
72
|
|
|
73
|
-
`--headless` escolhe somente o scaffold inicial; não há um comando `agent-native` que converta no lugar um projeto existente apenas com actions em um app de
|
|
73
|
+
`--headless` escolhe somente o scaffold inicial; não há um comando `agent-native` que converta no lugar um projeto existente apenas com actions em um app de navegador. Quando as pessoas precisarem de uma UI, use o [template Chat](/docs/template-chat) como referência de migração: mantenha as actions, os dados SQL, as instructions e as skills e adicione o shell do navegador e os server plugins. Para uma superfície de chat, adicione `<AgentChatSurface>` e um chat plugin que carregue as actions existentes. Verifique `pnpm typecheck` e um fluxo de UI que chame uma action existente. `agent-native add` é para integration blueprints, não para essa conversão.
|
|
74
74
|
|
|
75
75
|
## O que vem a seguir
|
|
76
76
|
|
|
@@ -66,9 +66,9 @@ Automation-first app 是无浏览器的 Agent-Native app,用于 scheduled jobs
|
|
|
66
66
|
|
|
67
67
|
→ [**Agent Surfaces — Headless agent**](/docs/agent-surfaces#headless)
|
|
68
68
|
|
|
69
|
-
##
|
|
69
|
+
## 稍后添加 UI {#add-chat-later}
|
|
70
70
|
|
|
71
|
-
`--headless` 只选择初始 scaffold;没有可将现有仅 actions
|
|
71
|
+
`--headless` 只选择初始 scaffold;没有可将现有仅 actions 项目原地转换为浏览器应用的 `agent-native` 命令。当用户需要 UI 时,请将 [Chat 模板](/docs/template-chat) 用作迁移参考:保留 actions、SQL 数据、instructions 和 skills,并添加浏览器 shell 和 server plugins。对于聊天界面,再添加 `<AgentChatSurface>` 以及加载现有 actions 的 chat plugin。验证 `pnpm typecheck`,并完成一次调用现有 action 的 UI 流程。`agent-native add` 用于 integration blueprints,不用于此转换。
|
|
72
72
|
|
|
73
73
|
## 下一步是什么
|
|
74
74
|
|
|
@@ -66,9 +66,9 @@ Automation-first app 是無瀏覽器的 Agent-Native app,用於 scheduled jobs
|
|
|
66
66
|
|
|
67
67
|
→ [**Agent Surfaces — Headless agent**](/docs/agent-surfaces#headless)
|
|
68
68
|
|
|
69
|
-
##
|
|
69
|
+
## 稍後新增 UI {#add-chat-later}
|
|
70
70
|
|
|
71
|
-
`--headless` 只會選擇初始 scaffold;沒有可將現有僅 actions
|
|
71
|
+
`--headless` 只會選擇初始 scaffold;沒有可將現有僅 actions 專案原地轉換為瀏覽器應用程式的 `agent-native` 命令。當使用者需要 UI 時,請將 [Chat 範本](/docs/template-chat) 作為遷移參考:保留 actions、SQL 資料、instructions 和 skills,並新增瀏覽器 shell 與 server plugins。對於聊天介面,再新增 `<AgentChatSurface>` 與載入現有 actions 的 chat plugin。請驗證 `pnpm typecheck`,並完成一次呼叫現有 action 的 UI 流程。`agent-native add` 用於 integration blueprints,不用於此轉換。
|
|
72
72
|
|
|
73
73
|
## 下一步是什麼
|
|
74
74
|
|
|
@@ -95,22 +95,66 @@ The `--headless` flag only chooses the initial scaffold. It does not choose a
|
|
|
95
95
|
different architecture. You can later move the same `actions/`, instructions,
|
|
96
96
|
skills, and SQL state into a chat app or full application surface.
|
|
97
97
|
|
|
98
|
-
## Add
|
|
98
|
+
## Add a UI later {#add-chat-later}
|
|
99
99
|
|
|
100
100
|
`--headless` is not a permanent runtime mode, and there is no `agent-native`
|
|
101
|
-
command that converts an existing action-only project into a
|
|
102
|
-
When people need a
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
101
|
+
command that converts an existing action-only project into a browser app in
|
|
102
|
+
place. When people need a UI, use the [Chat template](/docs/template-chat) as
|
|
103
|
+
the migration reference and keep the durable action and database contract:
|
|
104
|
+
|
|
105
|
+
<Diagram id="doc-block-ui-later" title="One contract, multiple surfaces" summary={"Start with a headless workflow, then add a browser UI without rewriting the actions, data, instructions, or skills underneath."}>
|
|
106
|
+
|
|
107
|
+
```html
|
|
108
|
+
<div class="diagram-ui-path">
|
|
109
|
+
<div class="diagram-panel" data-rough>
|
|
110
|
+
<span class="diagram-pill accent">Start headless</span
|
|
111
|
+
><small class="diagram-muted">jobs · CLI · external agents</small>
|
|
112
|
+
</div>
|
|
113
|
+
<div class="diagram-arrow diagram-muted" aria-hidden="true">→</div>
|
|
114
|
+
<div class="diagram-panel" data-rough>
|
|
115
|
+
<span class="diagram-pill">Add a UI</span
|
|
116
|
+
><small class="diagram-muted">chat · routes · durable pages</small>
|
|
117
|
+
</div>
|
|
118
|
+
<div class="diagram-arrow diagram-muted" aria-hidden="true">→</div>
|
|
119
|
+
<div class="diagram-panel ok" data-rough>
|
|
120
|
+
<span class="diagram-pill ok">Keep the contract</span
|
|
121
|
+
><small class="diagram-muted">actions · SQL · instructions · skills</small>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
```css
|
|
127
|
+
.diagram-ui-path {
|
|
128
|
+
display: flex;
|
|
129
|
+
align-items: center;
|
|
130
|
+
justify-content: center;
|
|
131
|
+
gap: 14px;
|
|
132
|
+
flex-wrap: wrap;
|
|
133
|
+
}
|
|
134
|
+
.diagram-ui-path .diagram-panel {
|
|
135
|
+
display: flex;
|
|
136
|
+
min-width: 150px;
|
|
137
|
+
flex-direction: column;
|
|
138
|
+
align-items: center;
|
|
139
|
+
gap: 4px;
|
|
140
|
+
text-align: center;
|
|
141
|
+
}
|
|
142
|
+
.diagram-ui-path .diagram-arrow {
|
|
143
|
+
font-size: 22px;
|
|
144
|
+
line-height: 1;
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
</Diagram>
|
|
149
|
+
|
|
150
|
+
1. Scaffold the first workflow with `npx @agent-native/core@latest create my-agent --headless`.
|
|
151
|
+
2. When a browser surface becomes useful, bring in the template's shell, server
|
|
152
|
+
plugins, and UI dependencies. For chat, that includes `<AgentChatSurface>`
|
|
153
|
+
and an `agent-chat` plugin that loads the existing action registry.
|
|
154
|
+
3. Keep your action names, Zod schemas, SQL data, instructions, and skills. A
|
|
155
|
+
UI calls those same actions; it does not replace them.
|
|
156
|
+
4. Verify both surfaces with `pnpm typecheck`, `pnpm dev`, and one UI flow that
|
|
157
|
+
calls an existing action.
|
|
114
158
|
|
|
115
159
|
Use `agent-native add` for integration blueprints, not for this conversion.
|
|
116
160
|
Choose chat when people need conversational steering, approvals, or durable
|
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.101.
|
|
3
|
+
"version": "0.101.6",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -373,6 +373,14 @@ interface DefineActionWithSchema<
|
|
|
373
373
|
* Only set this for mutating actions that are internally concurrency-safe
|
|
374
374
|
* and order-independent for same-turn execution. */
|
|
375
375
|
parallelSafe?: boolean;
|
|
376
|
+
/** Set false to exempt a read-only tool from the agent loop's duplicate
|
|
377
|
+
* read-only call guard (per-turn result cache + "Skipped duplicate..."
|
|
378
|
+
* repeat detection). Default true (deduped). Use this for volatile/polling
|
|
379
|
+
* reads where an identical call is expected to return a different result
|
|
380
|
+
* each time — e.g. polling a code-execution status by id, or re-fetching
|
|
381
|
+
* current on-screen state. Has no effect on non-read-only actions, which
|
|
382
|
+
* are never deduped in the first place. */
|
|
383
|
+
dedupe?: boolean;
|
|
376
384
|
/** Whether this action may be invoked from the tools (Alpine iframe) bridge
|
|
377
385
|
* via `appAction(name, params)` — see `packages/core/docs/content/actions.mdx`
|
|
378
386
|
* ("Tools Callability"). **Default-allow opt-out**: undefined / `true` both
|
|
@@ -493,6 +501,9 @@ interface DefineActionWithParams<
|
|
|
493
501
|
/** If true, the agent may execute this action concurrently with other
|
|
494
502
|
* read-only or parallel-safe tool calls emitted in the same model turn. */
|
|
495
503
|
parallelSafe?: boolean;
|
|
504
|
+
/** Set false to exempt a read-only tool from the duplicate read-only call
|
|
505
|
+
* guard. Default true. See the schema overload above. */
|
|
506
|
+
dedupe?: boolean;
|
|
496
507
|
/** Whether this action may be invoked from the tools (Alpine iframe) bridge
|
|
497
508
|
* via `appAction(name, params)`. See the schema overload above for details
|
|
498
509
|
* and the `toolCallable` section in actions.md. */
|
|
@@ -557,6 +568,7 @@ export interface ActionDefinition<TInput, TReturn> {
|
|
|
557
568
|
readonly readOnly?: boolean;
|
|
558
569
|
readonly allowInPlanMode?: boolean;
|
|
559
570
|
readonly parallelSafe?: boolean;
|
|
571
|
+
readonly dedupe?: boolean;
|
|
560
572
|
readonly toolCallable?: boolean;
|
|
561
573
|
readonly publicAgent?: PublicAgentActionConfig;
|
|
562
574
|
readonly link?: ActionLinkBuilder;
|
|
@@ -752,6 +764,8 @@ export function defineAction(options: any) {
|
|
|
752
764
|
typeof options.parallelSafe === "boolean"
|
|
753
765
|
? options.parallelSafe
|
|
754
766
|
: undefined;
|
|
767
|
+
const dedupe: boolean | undefined =
|
|
768
|
+
typeof options.dedupe === "boolean" ? options.dedupe : undefined;
|
|
755
769
|
const publicAgent: PublicAgentActionConfig | undefined =
|
|
756
770
|
options.publicAgent &&
|
|
757
771
|
typeof options.publicAgent === "object" &&
|
|
@@ -806,6 +820,7 @@ export function defineAction(options: any) {
|
|
|
806
820
|
? { allowInPlanMode: options.allowInPlanMode }
|
|
807
821
|
: {}),
|
|
808
822
|
...(typeof parallelSafe === "boolean" ? { parallelSafe } : {}),
|
|
823
|
+
...(typeof dedupe === "boolean" ? { dedupe } : {}),
|
|
809
824
|
...(typeof toolCallable === "boolean" ? { toolCallable } : {}),
|
|
810
825
|
...(publicAgent ? { publicAgent } : {}),
|
|
811
826
|
...(link ? { link } : {}),
|
|
@@ -583,6 +583,11 @@ export interface ActionEntry {
|
|
|
583
583
|
* read-only/parallel-safe tool calls. Only use for actions that handle
|
|
584
584
|
* their own write ordering and idempotency. */
|
|
585
585
|
parallelSafe?: boolean;
|
|
586
|
+
/** Set false to exempt a read-only tool from the duplicate read-only
|
|
587
|
+
* tool-call guard (per-turn result cache + repeat-kill). Default true. Use
|
|
588
|
+
* for volatile/polling reads that are expected to return a different
|
|
589
|
+
* result on each identical call. See `defineAction`'s `dedupe` option. */
|
|
590
|
+
dedupe?: boolean;
|
|
586
591
|
/** Whether this action may be invoked from the tools-iframe bridge.
|
|
587
592
|
* **Default-allow opt-out**: only an explicit `false` returns 403.
|
|
588
593
|
* - `true` / `undefined` — allow.
|
|
@@ -2170,16 +2175,26 @@ function seedReadOnlyToolResultsFromHistory(
|
|
|
2170
2175
|
const cache = new Map<string, string>();
|
|
2171
2176
|
if (!isInternalContinuationTurn(messages)) return cache;
|
|
2172
2177
|
|
|
2173
|
-
|
|
2174
|
-
|
|
2178
|
+
// Scoped to the current turn only (same slice as
|
|
2179
|
+
// seedWriteToolInterruptionsFromHistory) — reads from a prior turn are no
|
|
2180
|
+
// longer relevant context and must not seed skip-as-duplicate behavior.
|
|
2181
|
+
const turnStart = findCurrentTurnStartForContinuation(messages);
|
|
2182
|
+
const turnMessages = messages.slice(turnStart);
|
|
2183
|
+
|
|
2184
|
+
const pendingToolCalls = new Map<
|
|
2185
|
+
string,
|
|
2186
|
+
{ name: string; input: unknown; readOnly: boolean; dedupe: boolean }
|
|
2187
|
+
>();
|
|
2188
|
+
for (const message of turnMessages) {
|
|
2175
2189
|
if (message.role === "assistant") {
|
|
2176
2190
|
for (const part of message.content) {
|
|
2177
2191
|
if (part.type !== "tool-call") continue;
|
|
2178
2192
|
const entry = actions[part.name];
|
|
2179
|
-
if (entry?.readOnly !== true) continue;
|
|
2180
2193
|
pendingToolCalls.set(part.id, {
|
|
2181
2194
|
name: part.name,
|
|
2182
2195
|
input: part.input,
|
|
2196
|
+
readOnly: entry?.readOnly === true,
|
|
2197
|
+
dedupe: entry?.dedupe !== false,
|
|
2183
2198
|
});
|
|
2184
2199
|
}
|
|
2185
2200
|
continue;
|
|
@@ -2189,6 +2204,17 @@ function seedReadOnlyToolResultsFromHistory(
|
|
|
2189
2204
|
if (part.type !== "tool-result") continue;
|
|
2190
2205
|
const call = pendingToolCalls.get(part.toolCallId);
|
|
2191
2206
|
if (!call) continue;
|
|
2207
|
+
if (!call.readOnly) {
|
|
2208
|
+
// Mirror the live loop: a successful write invalidates all cached
|
|
2209
|
+
// reads (see the `readOnlyToolResultCache.clear()` call below), so a
|
|
2210
|
+
// read seeded from before an intervening write must not be replayed
|
|
2211
|
+
// as still-fresh.
|
|
2212
|
+
if (part.isError !== true) cache.clear();
|
|
2213
|
+
continue;
|
|
2214
|
+
}
|
|
2215
|
+
// dedupe:false read-only tools (volatile/polling reads) are never
|
|
2216
|
+
// cached — every call must execute fresh, seeded or not.
|
|
2217
|
+
if (!call.dedupe) continue;
|
|
2192
2218
|
if (!isReusableReadOnlyToolResult(part)) continue;
|
|
2193
2219
|
cache.set(toolCallCacheKey(call.name, call.input), part.content);
|
|
2194
2220
|
}
|
|
@@ -2197,11 +2223,100 @@ function seedReadOnlyToolResultsFromHistory(
|
|
|
2197
2223
|
return cache;
|
|
2198
2224
|
}
|
|
2199
2225
|
|
|
2226
|
+
function visibleDuplicateReadOnlyToolResult(toolName: string): string {
|
|
2227
|
+
return (
|
|
2228
|
+
`Skipped duplicate read-only call to ${toolName}: identical input already ran in this turn. ` +
|
|
2229
|
+
`Use the previous result already in the conversation instead of calling this tool again.`
|
|
2230
|
+
);
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
function resurfacedDuplicateReadOnlyToolResultPrefix(toolName: string): string {
|
|
2234
|
+
return (
|
|
2235
|
+
`Skipped duplicate read-only call to ${toolName}: identical input already ran in this turn. ` +
|
|
2236
|
+
`Its earlier result is no longer in view, so here it is again:\n\n`
|
|
2237
|
+
);
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
function resurfacedDuplicateReadOnlyToolResult(
|
|
2241
|
+
toolName: string,
|
|
2242
|
+
cachedResult: string,
|
|
2243
|
+
): string {
|
|
2244
|
+
return `${resurfacedDuplicateReadOnlyToolResultPrefix(toolName)}${cachedResult}`;
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
/** Restore visible-repeat strike counts for this continuation's active turn. */
|
|
2248
|
+
function seedDuplicateReadOnlyToolCallsFromHistory(
|
|
2249
|
+
messages: EngineMessage[],
|
|
2250
|
+
actions: Record<string, ActionEntry>,
|
|
2251
|
+
): Map<string, number> {
|
|
2252
|
+
const repeats = new Map<string, number>();
|
|
2253
|
+
if (!isInternalContinuationTurn(messages)) return repeats;
|
|
2254
|
+
|
|
2255
|
+
const turnStart = findCurrentTurnStartForContinuation(messages);
|
|
2256
|
+
const pendingToolCalls = new Map<
|
|
2257
|
+
string,
|
|
2258
|
+
{ name: string; input: unknown; readOnly: boolean; dedupe: boolean }
|
|
2259
|
+
>();
|
|
2260
|
+
const reusableReadKeys = new Set<string>();
|
|
2261
|
+
|
|
2262
|
+
for (const message of messages.slice(turnStart)) {
|
|
2263
|
+
if (message.role === "assistant") {
|
|
2264
|
+
for (const part of message.content) {
|
|
2265
|
+
if (part.type !== "tool-call") continue;
|
|
2266
|
+
const entry = actions[part.name];
|
|
2267
|
+
pendingToolCalls.set(part.id, {
|
|
2268
|
+
name: part.name,
|
|
2269
|
+
input: part.input,
|
|
2270
|
+
readOnly: entry?.readOnly === true,
|
|
2271
|
+
dedupe: entry?.dedupe !== false,
|
|
2272
|
+
});
|
|
2273
|
+
}
|
|
2274
|
+
continue;
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
for (const part of message.content) {
|
|
2278
|
+
if (part.type !== "tool-result") continue;
|
|
2279
|
+
const call = pendingToolCalls.get(part.toolCallId);
|
|
2280
|
+
if (!call) continue;
|
|
2281
|
+
if (!call.readOnly) {
|
|
2282
|
+
if (part.isError !== true) {
|
|
2283
|
+
repeats.clear();
|
|
2284
|
+
reusableReadKeys.clear();
|
|
2285
|
+
}
|
|
2286
|
+
continue;
|
|
2287
|
+
}
|
|
2288
|
+
if (!call.dedupe || part.isError === true) continue;
|
|
2289
|
+
|
|
2290
|
+
const cacheKey = toolCallCacheKey(call.name, call.input);
|
|
2291
|
+
if (part.content === visibleDuplicateReadOnlyToolResult(call.name)) {
|
|
2292
|
+
if (reusableReadKeys.has(cacheKey)) {
|
|
2293
|
+
repeats.set(cacheKey, (repeats.get(cacheKey) ?? 0) + 1);
|
|
2294
|
+
}
|
|
2295
|
+
continue;
|
|
2296
|
+
}
|
|
2297
|
+
if (
|
|
2298
|
+
part.content.startsWith(
|
|
2299
|
+
resurfacedDuplicateReadOnlyToolResultPrefix(call.name),
|
|
2300
|
+
)
|
|
2301
|
+
) {
|
|
2302
|
+
if (reusableReadKeys.has(cacheKey)) repeats.set(cacheKey, 0);
|
|
2303
|
+
continue;
|
|
2304
|
+
}
|
|
2305
|
+
if (isReusableReadOnlyToolResult(part)) {
|
|
2306
|
+
reusableReadKeys.add(cacheKey);
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2311
|
+
return repeats;
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2200
2314
|
function isReusableReadOnlyToolResult(part: EngineToolResultPart): boolean {
|
|
2201
2315
|
if (part.isError) return false;
|
|
2202
2316
|
const lower = part.content.trim().toLowerCase();
|
|
2203
2317
|
if (!lower) return false;
|
|
2204
2318
|
return !(
|
|
2319
|
+
lower.startsWith("skipped duplicate read-only call to ") ||
|
|
2205
2320
|
lower.startsWith("invalid action parameters for ") ||
|
|
2206
2321
|
lower.startsWith("error running ") ||
|
|
2207
2322
|
lower.includes("run aborted") ||
|
|
@@ -2211,6 +2326,56 @@ function isReusableReadOnlyToolResult(part: EngineToolResultPart): boolean {
|
|
|
2211
2326
|
);
|
|
2212
2327
|
}
|
|
2213
2328
|
|
|
2329
|
+
/**
|
|
2330
|
+
* Whether a cached read-only tool result is still something the model can
|
|
2331
|
+
* actually see in `contextMessages` — the trimmed/summarized view the engine
|
|
2332
|
+
* is streamed (NOT the raw, ever-growing `messages` array the cache is keyed
|
|
2333
|
+
* off of). Context-xray `evict` drops tool-result parts entirely, `summarize`
|
|
2334
|
+
* replaces their content with a placeholder, and observational-memory
|
|
2335
|
+
* trimming drops whole older messages once active. When the cached result
|
|
2336
|
+
* has fallen out of that view, re-serving "use the previous result" is not
|
|
2337
|
+
* actionable — the model has nothing to point back to.
|
|
2338
|
+
*
|
|
2339
|
+
* A visible result must belong to the same tool name + normalized input and
|
|
2340
|
+
* contain either the exact cached body or the exact wrapper used when that
|
|
2341
|
+
* body was re-served after trimming. Matching only by a content suffix is too
|
|
2342
|
+
* loose: short results such as "ok" can also end unrelated tool output.
|
|
2343
|
+
*/
|
|
2344
|
+
export function isCachedToolResultVisibleInContext(
|
|
2345
|
+
contextMessages: EngineMessage[],
|
|
2346
|
+
toolCall: { name: string; input: unknown },
|
|
2347
|
+
cachedResult: string,
|
|
2348
|
+
): boolean {
|
|
2349
|
+
if (cachedResult.length === 0) return true;
|
|
2350
|
+
const cacheKey = toolCallCacheKey(toolCall.name, toolCall.input);
|
|
2351
|
+
const matchingToolCallIds = new Set<string>();
|
|
2352
|
+
for (const message of contextMessages) {
|
|
2353
|
+
if (message.role !== "assistant") continue;
|
|
2354
|
+
for (const part of message.content) {
|
|
2355
|
+
if (part.type !== "tool-call") continue;
|
|
2356
|
+
if (toolCallCacheKey(part.name, part.input) === cacheKey) {
|
|
2357
|
+
matchingToolCallIds.add(part.id);
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
const resurfacedResult = resurfacedDuplicateReadOnlyToolResult(
|
|
2363
|
+
toolCall.name,
|
|
2364
|
+
cachedResult,
|
|
2365
|
+
);
|
|
2366
|
+
for (const message of contextMessages) {
|
|
2367
|
+
if (message.role !== "user") continue;
|
|
2368
|
+
for (const part of message.content) {
|
|
2369
|
+
if (part.type !== "tool-result") continue;
|
|
2370
|
+
if (!matchingToolCallIds.has(part.toolCallId)) continue;
|
|
2371
|
+
if (part.content === cachedResult || part.content === resurfacedResult) {
|
|
2372
|
+
return true;
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
return false;
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2214
2379
|
/**
|
|
2215
2380
|
* Counts how many times each write (non-read-only) tool call was interrupted
|
|
2216
2381
|
* before returning a result in the continuation history. When a connection
|
|
@@ -3106,7 +3271,10 @@ export async function runAgentLoop(opts: {
|
|
|
3106
3271
|
messages,
|
|
3107
3272
|
actions,
|
|
3108
3273
|
);
|
|
3109
|
-
const duplicateReadOnlyToolCalls =
|
|
3274
|
+
const duplicateReadOnlyToolCalls = seedDuplicateReadOnlyToolCallsFromHistory(
|
|
3275
|
+
messages,
|
|
3276
|
+
actions,
|
|
3277
|
+
);
|
|
3110
3278
|
const writeToolInterruptions = seedWriteToolInterruptionsFromHistory(
|
|
3111
3279
|
messages,
|
|
3112
3280
|
actions,
|
|
@@ -4339,18 +4507,49 @@ export async function runAgentLoop(opts: {
|
|
|
4339
4507
|
};
|
|
4340
4508
|
}
|
|
4341
4509
|
|
|
4510
|
+
// dedupe: false opts a read-only tool out of the guard entirely — the
|
|
4511
|
+
// cacheKey stays null so it never gets skipped-as-duplicate and never
|
|
4512
|
+
// populates the cache (see the success handler below, which also
|
|
4513
|
+
// leaves dedupe:false results uncached and un-cleared).
|
|
4342
4514
|
const cacheKey =
|
|
4343
|
-
actionEntry.readOnly === true
|
|
4515
|
+
actionEntry.readOnly === true && actionEntry.dedupe !== false
|
|
4344
4516
|
? toolCallCacheKey(toolCall.name, toolCall.input)
|
|
4345
4517
|
: null;
|
|
4346
4518
|
if (cacheKey && readOnlyToolResultCache.has(cacheKey)) {
|
|
4347
|
-
const repeats = (duplicateReadOnlyToolCalls.get(cacheKey) ?? 0) + 1;
|
|
4348
|
-
duplicateReadOnlyToolCalls.set(cacheKey, repeats);
|
|
4349
4519
|
const previousResult = readOnlyToolResultCache.get(cacheKey) ?? "";
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4520
|
+
// `contextMessages` (not `messages`) is what the model actually sees
|
|
4521
|
+
// this iteration — context-xray eviction/summarization and
|
|
4522
|
+
// observational-memory trimming can drop the earlier result from
|
|
4523
|
+
// view even though it's still cached here. Only strike-count the
|
|
4524
|
+
// repeat when the model could have looked back and found it itself.
|
|
4525
|
+
const visible = isCachedToolResultVisibleInContext(
|
|
4526
|
+
contextMessages,
|
|
4527
|
+
toolCall,
|
|
4528
|
+
previousResult,
|
|
4529
|
+
);
|
|
4530
|
+
let result: string;
|
|
4531
|
+
if (visible) {
|
|
4532
|
+
const repeats = (duplicateReadOnlyToolCalls.get(cacheKey) ?? 0) + 1;
|
|
4533
|
+
duplicateReadOnlyToolCalls.set(cacheKey, repeats);
|
|
4534
|
+
result = visibleDuplicateReadOnlyToolResult(toolCall.name);
|
|
4535
|
+
if (repeats >= 3) {
|
|
4536
|
+
requestedActionStop ??= {
|
|
4537
|
+
message:
|
|
4538
|
+
"I stopped because the agent kept asking for the same read-only context it already had. Please send the request again if you want me to retry from a fresh turn.",
|
|
4539
|
+
errorCode: "duplicate_read_only_tool",
|
|
4540
|
+
};
|
|
4541
|
+
}
|
|
4542
|
+
} else {
|
|
4543
|
+
// The earlier result was trimmed out of the model's visible
|
|
4544
|
+
// context — this isn't a repetitive loop, the model legitimately
|
|
4545
|
+
// can't see the answer anymore. Re-serve it in full and don't
|
|
4546
|
+
// count a strike.
|
|
4547
|
+
duplicateReadOnlyToolCalls.set(cacheKey, 0);
|
|
4548
|
+
result = resurfacedDuplicateReadOnlyToolResult(
|
|
4549
|
+
toolCall.name,
|
|
4550
|
+
previousResult,
|
|
4551
|
+
);
|
|
4552
|
+
}
|
|
4354
4553
|
send({
|
|
4355
4554
|
type: "tool_done",
|
|
4356
4555
|
id: toolCall.id,
|
|
@@ -4360,13 +4559,6 @@ export async function runAgentLoop(opts: {
|
|
|
4360
4559
|
completedSideEffect: false,
|
|
4361
4560
|
});
|
|
4362
4561
|
recordToolResult(result, false);
|
|
4363
|
-
if (repeats >= 3) {
|
|
4364
|
-
requestedActionStop ??= {
|
|
4365
|
-
message:
|
|
4366
|
-
"I stopped because the agent kept asking for the same read-only context it already had. Please send the request again if you want me to retry from a fresh turn.",
|
|
4367
|
-
errorCode: "duplicate_read_only_tool",
|
|
4368
|
-
};
|
|
4369
|
-
}
|
|
4370
4562
|
return {
|
|
4371
4563
|
type: "tool-result" as const,
|
|
4372
4564
|
toolCallId: toolCall.id,
|
|
@@ -4638,7 +4830,11 @@ export async function runAgentLoop(opts: {
|
|
|
4638
4830
|
if (!isError) {
|
|
4639
4831
|
if (cacheKey) {
|
|
4640
4832
|
readOnlyToolResultCache.set(cacheKey, result);
|
|
4641
|
-
} else {
|
|
4833
|
+
} else if (actionEntry.readOnly !== true) {
|
|
4834
|
+
// A genuine write invalidates all cached reads. A dedupe:false
|
|
4835
|
+
// read-only tool also has a null cacheKey (see above) but must NOT
|
|
4836
|
+
// clear the cache — it isn't a write and other tools' cached reads
|
|
4837
|
+
// are still valid.
|
|
4642
4838
|
readOnlyToolResultCache.clear();
|
|
4643
4839
|
duplicateReadOnlyToolCalls.clear();
|
|
4644
4840
|
}
|