@circuitwall/jarela 2.3.0 → 2.5.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/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-path-routes-manifest.json +5 -5
- package/.next/standalone/.next/build-manifest.json +2 -2
- package/.next/standalone/.next/prerender-manifest.json +4 -4
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +14 -2
- package/.next/standalone/.next/server/app/_not-found.rsc +16 -4
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +16 -4
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +16 -4
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/v1/access/[identity]/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/access/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/agents/[id]/compact/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/agents/[id]/display-filters/route.js +17 -5
- package/.next/standalone/.next/server/app/api/v1/agents/[id]/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/agents/[id]/thread/route.js +17 -5
- package/.next/standalone/.next/server/app/api/v1/agents/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/allowed-sites/[hostname]/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/allowed-sites/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/artifacts/lifecycle/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/chats/route.js +26 -32
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/chats/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/ignores/[remote_jid]/route.js +18 -7
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/ignores/route.js +18 -7
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/lookup/route.js +26 -32
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/lookup/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/pair/route.js +27 -33
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/pair/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/route.js +26 -32
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/routes/[route_id]/route.js +28 -12
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/routes/route.js +28 -12
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/status/route.js +26 -32
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/bridges/kinds/route.js +9 -2
- package/.next/standalone/.next/server/app/api/v1/bridges/kinds/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/bridges/route.js +19 -7
- package/.next/standalone/.next/server/app/api/v1/bridges/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/dashboard/metrics/route.js +17 -5
- package/.next/standalone/.next/server/app/api/v1/dashboard/pricing/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/dashboard/pricing-task/route.js +1 -1
- package/.next/standalone/.next/server/app/api/v1/documents/search/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/documents/settings/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/documents/sources/[id]/route.js +1 -1
- package/.next/standalone/.next/server/app/api/v1/documents/sources/route.js +1 -1
- package/.next/standalone/.next/server/app/api/v1/env-sync/allowlist/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/env-sync/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/events/route.js +26 -32
- package/.next/standalone/.next/server/app/api/v1/events/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/extension/agents/route.js +1 -1
- package/.next/standalone/.next/server/app/api/v1/extension/browser/activate/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/extension/browser/foreground/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/extension/browser/history/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/extension/browser/poll/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/extension/browser/progress/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/extension/browser/result/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/extension/browser/retry/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/extension/browser/status/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/extension/browser/tabs/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/extension/fill/route.js +1 -1
- package/.next/standalone/.next/server/app/api/v1/extension/refine/route.js +1 -1
- package/.next/standalone/.next/server/app/api/v1/extension/turn/route.js +1 -1
- package/.next/standalone/.next/server/app/api/v1/extensions/route.js +1 -1
- package/.next/standalone/.next/server/app/api/v1/extensions/tools/[name]/config/route.js +1 -1
- package/.next/standalone/.next/server/app/api/v1/extensions/tools/[name]/route.js +1 -1
- package/.next/standalone/.next/server/app/api/v1/extensions/tools/[name]/secrets/route.js +1 -1
- package/.next/standalone/.next/server/app/api/v1/harnesses/[id]/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/harnesses/default/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/harnesses/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/health/route.js +17 -5
- package/.next/standalone/.next/server/app/api/v1/integrations/[name]/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/integrations/gmail/oauth/callback/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/integrations/gmail/oauth/start/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/integrations/gmail/oauth/status/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/integrations/outlook/oauth/callback/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/integrations/outlook/oauth/start/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/integrations/outlook/oauth/status/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/integrations/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/lifecycle/adoption/route.js +17 -5
- package/.next/standalone/.next/server/app/api/v1/maps/embed/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/mcp-servers/[name]/route.js +17 -5
- package/.next/standalone/.next/server/app/api/v1/mcp-servers/route.js +17 -5
- package/.next/standalone/.next/server/app/api/v1/memory/[namespace]/[key]/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/memory/policy/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/memory/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/memory/search/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/models/[name]/compact-threads/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/models/[name]/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/models/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/packages/defaults/[id]/route.js +28 -4
- package/.next/standalone/.next/server/app/api/v1/packages/reload/route.js +28 -4
- package/.next/standalone/.next/server/app/api/v1/packages/route.js +28 -4
- package/.next/standalone/.next/server/app/api/v1/page-capture/route.js +1 -1
- package/.next/standalone/.next/server/app/api/v1/pending-actions/[id]/approve/route.js +27 -12
- package/.next/standalone/.next/server/app/api/v1/profile/location/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/profile/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/providers/[provider]/models/route.js +585 -58
- package/.next/standalone/.next/server/app/api/v1/providers/[provider]/probe/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/providers/github-copilot/auth/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/providers/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/proxy-config/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/redaction/init/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/redaction/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/scheduled-tasks/[id]/route.js +17 -5
- package/.next/standalone/.next/server/app/api/v1/scheduled-tasks/[id]/run/route.js +1 -1
- package/.next/standalone/.next/server/app/api/v1/scheduled-tasks/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/security/idle-timeout/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/security/pin/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/security/state/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/security/unlock/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/security/verify-pin/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/skills/[id]/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/skills/repos/[id]/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/skills/repos/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/skills/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/tasks/[agent_id]/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/tasks/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/activity/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/context-pin/route.js +12 -6
- package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/route.js +125 -26
- package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/run/route.js +1 -2
- package/.next/standalone/.next/server/app/api/v1/threads/route.js +17 -5
- package/.next/standalone/.next/server/app/api/v1/tools/route.js +1 -1
- package/.next/standalone/.next/server/app/api/v1/update/apply/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/voice/transcribe/route.js +17 -5
- package/.next/standalone/.next/server/app/api/v1/voice/tts/route.js +17 -5
- package/.next/standalone/.next/server/app/api/v1/watchers/[id]/route.js +17 -5
- package/.next/standalone/.next/server/app/api/v1/watchers/[id]/run/route.js +1 -1
- package/.next/standalone/.next/server/app/api/v1/watchers/route.js +1 -1
- package/.next/standalone/.next/server/app/page.js +27323 -27034
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/setup/page.js +8 -0
- package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app-paths-manifest.json +5 -5
- package/.next/standalone/.next/server/chunks/1388.js +106 -36
- package/.next/standalone/.next/server/chunks/2068.js +47 -10
- package/.next/standalone/.next/server/chunks/210.js +6 -6
- package/.next/standalone/.next/server/chunks/2447.js +10 -5
- package/.next/standalone/.next/server/chunks/2653.js +80 -1
- package/.next/standalone/.next/server/chunks/3042.js +10 -8
- package/.next/standalone/.next/server/chunks/{4540.js → 309.js} +104 -38
- package/.next/standalone/.next/server/chunks/{2350.js → 3272.js} +104 -37
- package/.next/standalone/.next/server/chunks/3524.js +150 -67
- package/.next/standalone/.next/server/chunks/3566.js +1 -1
- package/.next/standalone/.next/server/chunks/4189.js +1 -1
- package/.next/standalone/.next/server/chunks/4596.js +28 -4
- package/.next/standalone/.next/server/chunks/46.js +91 -31
- package/.next/standalone/.next/server/chunks/466.js +53 -7
- package/.next/standalone/.next/server/chunks/4955.js +30 -3
- package/.next/standalone/.next/server/chunks/5585.js +28 -4
- package/.next/standalone/.next/server/chunks/5908.js +63 -42
- package/.next/standalone/.next/server/chunks/6142.js +10 -2
- package/.next/standalone/.next/server/chunks/8461.js +10 -8
- package/.next/standalone/.next/server/chunks/9032.js +5 -5
- package/.next/standalone/.next/server/chunks/9164.js +487 -177
- package/.next/standalone/.next/server/chunks/9304.js +51 -12
- package/.next/standalone/.next/server/chunks/{423.js → 9590.js} +84 -24
- package/.next/standalone/.next/server/middleware-build-manifest.js +2 -2
- package/.next/standalone/.next/server/middleware.js +89 -21
- package/.next/standalone/.next/server/pages/404.html +14 -2
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/preview-props.json +3 -3
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/{1395-e4a99912b8f83044.js → 1395-bfe1f3ef760c0f50.js} +54 -8
- package/.next/standalone/.next/static/chunks/app/{layout-9bc0b10127cb7d42.js → layout-7b24ad13faa3944b.js} +69 -2
- package/.next/standalone/.next/static/chunks/app/{page-213a623b37226452.js → page-2ef8d75d40eb4311.js} +10643 -10295
- package/.next/standalone/.next/static/css/c160ea862133acd2.css +5 -0
- package/.next/standalone/docs/adoption/changesets.json +2805 -0
- package/.next/standalone/package.json +1 -1
- package/CHANGELOG.md +52 -0
- package/api/client.models.test.ts +10 -0
- package/api/client.startup.test.ts +129 -0
- package/api/client.ts +51 -9
- package/api/thread-metadata.test.ts +72 -0
- package/api/types.ts +6 -3
- package/app/api/v1/providers/[provider]/models/route.test.ts +216 -6
- package/app/api/v1/providers/[provider]/models/route.ts +259 -40
- package/app/api/v1/threads/[thread_id]/route.ts +26 -23
- package/app/api/v1/threads/[thread_id]/run/route.ts +0 -1
- package/app/globals.css +119 -29
- package/app/layout.tsx +12 -0
- package/components/agents/AgentsPanel.tsx +5 -13
- package/components/chat/ChatView.tsx +9 -18
- package/components/chat/InputBar.tsx +1 -1
- package/components/chat/MessageBubble.tsx +13 -1
- package/components/chat/MessageList.test.tsx +45 -4
- package/components/chat/MessageList.tsx +123 -89
- package/components/chat/ToolList.tsx +88 -84
- package/components/chat/useThreadActivity.test.tsx +2 -2
- package/components/chat/useThreadActivity.ts +2 -2
- package/components/chat/useThreadData.ts +1 -30
- package/components/chat/useThreadMetadataPolling.test.tsx +128 -0
- package/components/chat/useThreadMetadataPolling.ts +50 -0
- package/components/chat/useUserProfileAndAgent.test.tsx +31 -0
- package/components/chat/useUserProfileAndAgent.ts +13 -2
- package/components/dashboard/DashboardPanel.tsx +1 -1
- package/components/dashboard/Series.tsx +34 -21
- package/components/dashboard/SummaryCards.tsx +1 -1
- package/components/integrations/NetworkSection.tsx +2 -2
- package/components/layout/AppShell.tsx +22 -7
- package/components/layout/MenuPanel.tsx +14 -24
- package/components/profile/ProfilePanel.tsx +2 -2
- package/components/settings/AppearancePanel.tsx +70 -3
- package/components/settings/SettingsPanel.tsx +2 -2
- package/components/tools/ToolsPanel.tsx +2 -7
- package/components/ui/AppearancePreferences.test.tsx +68 -0
- package/components/ui/BootScreen.test.tsx +44 -0
- package/components/ui/BootScreen.tsx +24 -11
- package/components/ui/NotificationStatus.test.tsx +2 -2
- package/components/ui/NotificationStatus.tsx +5 -5
- package/components/ui/SubTabBar.tsx +2 -3
- package/contexts/ThemeContext.tsx +84 -1
- package/hooks/useAgentSession.test.ts +12 -1
- package/hooks/useAgentSession.ts +6 -4
- package/hooks/useUrlSync.test.ts +13 -0
- package/hooks/useUrlSync.ts +0 -4
- package/lib/agents/citation-checker.test.ts +40 -2
- package/lib/agents/citation-checker.ts +56 -11
- package/lib/agents/context-boundary.ts +5 -6
- package/lib/agents/instruction-edits.test.ts +120 -0
- package/lib/agents/instruction-edits.ts +65 -42
- package/lib/agents/prepare/request.ts +0 -2
- package/lib/agents/prepare/system-prompt.test.ts +10 -0
- package/lib/agents/prepare/system-prompt.ts +8 -2
- package/lib/agents/proposals.test.ts +76 -0
- package/lib/agents/proposals.ts +13 -5
- package/lib/agents/run-thread.auto-boundary.test.ts +125 -5
- package/lib/agents/run-thread.ts +24 -9
- package/lib/agents/sqlite-checkpoint-saver.test.ts +91 -0
- package/lib/agents/warm-summary-background.ts +22 -2
- package/lib/agents/warm-topics.test.ts +155 -1
- package/lib/agents/warm-topics.ts +27 -7
- package/lib/bridges/dispatcher.ts +1 -0
- package/lib/bridges/message-role.test.ts +81 -0
- package/lib/bridges/message-role.ts +23 -10
- package/lib/bridges/types.ts +6 -0
- package/lib/bridges/whatsapp-link.test.ts +32 -0
- package/lib/bridges/whatsapp-link.ts +21 -0
- package/lib/bridges/whatsapp.inbound.test.ts +239 -0
- package/lib/bridges/whatsapp.sender.test.ts +80 -1
- package/lib/bridges/whatsapp.ts +76 -38
- package/lib/db/migrations.test.ts +61 -0
- package/lib/db/migrations.ts +13 -0
- package/lib/embeddings/performance.test.ts +114 -0
- package/lib/pricing/snapshot.test.ts +70 -0
- package/lib/pricing/snapshot.ts +10 -2
- package/lib/proxy/dispatcher.test.ts +119 -1
- package/lib/proxy/dispatcher.ts +22 -9
- package/lib/stores/agent-configs.ts +10 -5
- package/lib/stores/bridges.test.ts +81 -0
- package/lib/stores/bridges.ts +9 -8
- package/lib/stores/dashboard-metrics-cache.test.ts +99 -0
- package/lib/stores/dashboard-metrics.ts +30 -4
- package/lib/stores/message-embedding-jobs.test.ts +27 -0
- package/lib/stores/message-embedding-jobs.ts +5 -3
- package/lib/stores/message-usage.test.ts +21 -1
- package/lib/stores/pricing-cache.test.ts +31 -0
- package/lib/stores/pricing.ts +3 -0
- package/lib/stores/proxy-config.test.ts +120 -0
- package/lib/stores/proxy-config.ts +22 -4
- package/lib/stores/threads.ts +12 -8
- package/lib/tools/communications/calendar.test.ts +43 -2
- package/lib/tools/communications/calendar.ts +2 -0
- package/lib/tools/communications/gmail.test.ts +51 -1
- package/lib/tools/communications/gmail.ts +44 -2
- package/lib/tools/communications/outlook-calendar.ts +2 -0
- package/lib/tools/communications/outlook.test.ts +33 -2
- package/lib/tools/communications/outlook.ts +2 -0
- package/lib/tools/filesystem/edit-match.test.ts +133 -0
- package/lib/tools/filesystem/edit-match.ts +7 -2
- package/lib/tools/packages/default-packages.test.ts +21 -0
- package/lib/tools/packages/optional-package.test.ts +72 -0
- package/lib/tools/packages/optional-package.ts +33 -4
- package/lib/tools/support/result-refs.test.ts +131 -0
- package/lib/tools/support/result-refs.ts +41 -9
- package/lib/ui/navigate.test.ts +13 -0
- package/lib/ui/navigate.ts +10 -2
- package/lib/utils/source-link.test.ts +51 -0
- package/lib/utils/source-link.ts +45 -0
- package/package.json +1 -1
- package/.next/standalone/.next/static/css/312542a5725ba0d5.css +0 -5
- /package/.next/standalone/.next/static/{bMRK5y3ruT7c3WtBfCQOG → 544OFJV-q0RC6Gjgje4LT}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{bMRK5y3ruT7c3WtBfCQOG → 544OFJV-q0RC6Gjgje4LT}/_ssgManifest.js +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,58 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.5.0] - 2026-10-18
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Add appearance presets and accent-color choices, and reorganize configuration
|
|
15
|
+
panels for a more consistent workspace.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Let assistant replies use the available chat width and keep long tool names,
|
|
20
|
+
status indicators, expanded tool details, and the composer within narrow screens.
|
|
21
|
+
- Align configuration panel widths on desktop and mobile.
|
|
22
|
+
- Refresh provider model catalogs and capability metadata.
|
|
23
|
+
- Resolve optional tool packages from the workspace when their package entry
|
|
24
|
+
point is unavailable.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- Reuse short-lived startup reads between the agent picker and chat so the
|
|
29
|
+
first agent load warms profile, configuration, and conversation data without
|
|
30
|
+
duplicate requests. Writes and run submissions invalidate the handoff.
|
|
31
|
+
- Speed up chat pagination, dashboard date windows, and thread lists with
|
|
32
|
+
automatically created SQLite indexes. The first startup after upgrading a
|
|
33
|
+
large database may take longer while indexes build.
|
|
34
|
+
- Index latest usage, memory ordering, signal cleanup, background retention,
|
|
35
|
+
and expired leases; find the next embedding wakeup with indexed lookups
|
|
36
|
+
instead of scanning the active queue.
|
|
37
|
+
- Reduce chat refresh work with metadata-only, visibility-aware polling and
|
|
38
|
+
reuse historical message rows during streaming updates.
|
|
39
|
+
- Reuse unchanged dashboard computations briefly, avoid loading snapshotted
|
|
40
|
+
message bodies for token estimates, and skip rereading unchanged pricing
|
|
41
|
+
snapshots. Database writes and pricing changes invalidate server-side metrics.
|
|
42
|
+
|
|
43
|
+
## [2.4.0] - 2026-10-16
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
|
|
47
|
+
- **Link answers back to the source: Gmail, Outlook mail and calendar, and Google Calendar results carry a web link and a ready-made `cite_as` markdown link, and WhatsApp 1:1 bridge messages carry a `wa.me` chat link, so agents can point you at the exact email, event or chat. Group chats and single WhatsApp messages have no public link. Replies delivered to WhatsApp use bare URLs.**
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
|
|
51
|
+
- **Stop bridge-heavy threads from showing "Compacting" and running an empty compaction on every turn, and stop topic detection from re-running every turn after a hot-turn-limit pass.**
|
|
52
|
+
- **Always send localhost traffic (Ollama, the WhatsApp bridge, Jarela's own API) directly when an HTTP proxy is configured, accept IPv6 proxy hosts, and reject manual proxy hosts that include a scheme, port, path or credentials.**
|
|
53
|
+
- **Replay each WhatsApp chat's offline backlog separately so one active chat no longer causes others to lose messages, and keep known chat names when a contact sends a blank name.**
|
|
54
|
+
- **Keep tuned persona sliders when an approved proposal or tool edit updates an agent, apply `hot_turn_limit` from proposals, reject negative or fractional limits, and drop blank or duplicate tool names.**
|
|
55
|
+
- **Save facts about non-Latin subjects under their own memory keys instead of colliding, and stop replacing a fact from deleting an unrelated one.**
|
|
56
|
+
- **Keep distinct Outlook and Gmail links as separate citations, keep closing code fences and horizontal rules when deduplicating instruction lines, and fix fuzzy edits on CRLF files and paged reads that split multi-byte characters.**
|
|
57
|
+
|
|
58
|
+
### Adoption
|
|
59
|
+
|
|
60
|
+
- If an agent's instructions or skills tell it not to include links or URLs in replies, decide whether it should now cite sources with the provided `cite_as` and chat links.
|
|
61
|
+
- If you listed `localhost` or `127.0.0.1` in the proxy bypass list only to reach local services, you can remove them; loopback is always direct now.
|
|
10
62
|
## [2.3.0] - 2026-10-16
|
|
11
63
|
|
|
12
64
|
### Added
|
|
@@ -77,4 +77,14 @@ describe("api request HTTP cache mode", () => {
|
|
|
77
77
|
|
|
78
78
|
expect((fetchMock.mock.calls[0][1] as RequestInit).cache).toBe("default");
|
|
79
79
|
});
|
|
80
|
+
|
|
81
|
+
it("fetches live thread metadata without loading a message page", async () => {
|
|
82
|
+
const fetchMock = vi.fn().mockResolvedValue(jsonResponse({ thread_id: "t1" }));
|
|
83
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
84
|
+
|
|
85
|
+
await api.threads.metadata("t1");
|
|
86
|
+
|
|
87
|
+
expect(fetchMock.mock.calls[0][0]).toBe("/api/v1/threads/t1?view=metadata");
|
|
88
|
+
expect((fetchMock.mock.calls[0][1] as RequestInit).cache).toBe("no-store");
|
|
89
|
+
});
|
|
80
90
|
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
|
|
4
|
+
vi.mock("./runtime-config", () => ({
|
|
5
|
+
runtimeConfig: () => ({ httpMaxAttempts: 1, httpRequestTimeoutMs: 30_000 }),
|
|
6
|
+
}));
|
|
7
|
+
|
|
8
|
+
function json(body: unknown): Response {
|
|
9
|
+
return new Response(JSON.stringify(body), { headers: { "Content-Type": "application/json" } });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
describe("agent startup handoff", () => {
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
vi.resetModules();
|
|
15
|
+
});
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
vi.restoreAllMocks();
|
|
18
|
+
vi.unstubAllGlobals();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("shares all four startup reads between boot and chat without sharing mutable objects", async () => {
|
|
22
|
+
const { api } = await import("./client");
|
|
23
|
+
const fetch = vi.fn().mockImplementation(() => Promise.resolve(json({ nested: { value: "original" } })));
|
|
24
|
+
vi.stubGlobal("fetch", fetch);
|
|
25
|
+
const load = () => Promise.all([
|
|
26
|
+
api.profile.get({ startup: true }),
|
|
27
|
+
api.agents.get("agent", { startup: true }),
|
|
28
|
+
api.agents.getThread("agent", { startup: true }),
|
|
29
|
+
api.threads.get("thread", { startup: true }),
|
|
30
|
+
]);
|
|
31
|
+
const [boot, chat] = await Promise.all([load(), load()]);
|
|
32
|
+
expect(fetch).toHaveBeenCalledTimes(4);
|
|
33
|
+
expect(chat).toEqual(boot);
|
|
34
|
+
expect(chat[0]).not.toBe(boot[0]);
|
|
35
|
+
await load();
|
|
36
|
+
expect(fetch).toHaveBeenCalledTimes(4);
|
|
37
|
+
for (const [, init] of fetch.mock.calls) {
|
|
38
|
+
expect(init.cache).toBe("no-store");
|
|
39
|
+
expect(init).not.toHaveProperty("startup");
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("expires startup reads after five seconds and keeps ordinary reads fresh", async () => {
|
|
44
|
+
const { api } = await import("./client");
|
|
45
|
+
let now = 1_000;
|
|
46
|
+
vi.spyOn(Date, "now").mockImplementation(() => now);
|
|
47
|
+
const fetch = vi.fn().mockImplementation(() => Promise.resolve(json({ name: "profile" })));
|
|
48
|
+
vi.stubGlobal("fetch", fetch);
|
|
49
|
+
await api.profile.get({ startup: true });
|
|
50
|
+
now += 4_999;
|
|
51
|
+
await api.profile.get({ startup: true });
|
|
52
|
+
expect(fetch).toHaveBeenCalledTimes(1);
|
|
53
|
+
now += 1;
|
|
54
|
+
await api.profile.get({ startup: true });
|
|
55
|
+
expect(fetch).toHaveBeenCalledTimes(2);
|
|
56
|
+
await api.profile.get();
|
|
57
|
+
await api.profile.get({ startup: true });
|
|
58
|
+
expect(fetch).toHaveBeenCalledTimes(4);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("isolates nested transcript data between consumers", async () => {
|
|
62
|
+
const { api } = await import("./client");
|
|
63
|
+
vi.stubGlobal("fetch", vi.fn().mockResolvedValue(json({ messages: [{ id: "message" }] })));
|
|
64
|
+
const first = await api.threads.get("thread", { startup: true });
|
|
65
|
+
first.messages.length = 0;
|
|
66
|
+
const second = await api.threads.get("thread", { startup: true });
|
|
67
|
+
expect(second.messages).toEqual([{ id: "message" }]);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("invalidates pending warmups before writes and never caches failed reads", async () => {
|
|
71
|
+
const { api } = await import("./client");
|
|
72
|
+
let resolveOld!: (response: Response) => void;
|
|
73
|
+
const fetch = vi.fn()
|
|
74
|
+
.mockImplementationOnce(() => new Promise<Response>((resolve) => { resolveOld = resolve; }))
|
|
75
|
+
.mockResolvedValueOnce(json({ name: "updated" }))
|
|
76
|
+
.mockResolvedValueOnce(json({ name: "updated" }))
|
|
77
|
+
.mockResolvedValueOnce(new Response("failed", { status: 400 }))
|
|
78
|
+
.mockResolvedValueOnce(json({ name: "recovered" }));
|
|
79
|
+
vi.stubGlobal("fetch", fetch);
|
|
80
|
+
const old = api.profile.get({ startup: true });
|
|
81
|
+
await api.profile.update({ name: "updated" });
|
|
82
|
+
resolveOld(json({ name: "old" }));
|
|
83
|
+
await old;
|
|
84
|
+
expect(await api.profile.get({ startup: true })).toEqual({ name: "updated" });
|
|
85
|
+
await expect(api.agents.get("retry", { startup: true })).rejects.toThrow("400");
|
|
86
|
+
await expect(api.agents.get("retry", { startup: true })).resolves.toEqual({ name: "recovered" });
|
|
87
|
+
expect(fetch).toHaveBeenCalledTimes(5);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("clears the startup page before submitting a run", async () => {
|
|
91
|
+
const { api, submitRun } = await import("./client");
|
|
92
|
+
const fetch = vi.fn()
|
|
93
|
+
.mockResolvedValueOnce(json({ messages: [] }))
|
|
94
|
+
.mockResolvedValueOnce(new Response(null, { status: 202 }))
|
|
95
|
+
.mockResolvedValueOnce(json({ messages: [{ id: "new" }] }));
|
|
96
|
+
vi.stubGlobal("fetch", fetch);
|
|
97
|
+
await api.threads.get("thread", { startup: true });
|
|
98
|
+
await submitRun("thread", "hello", new AbortController().signal);
|
|
99
|
+
await expect(api.threads.get("thread", { startup: true })).resolves.toEqual({ messages: [{ id: "new" }] });
|
|
100
|
+
expect(fetch).toHaveBeenCalledTimes(3);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("discards startup data read while a write was still in flight", async () => {
|
|
104
|
+
const { api } = await import("./client");
|
|
105
|
+
let finishWrite!: (response: Response) => void;
|
|
106
|
+
const fetch = vi.fn()
|
|
107
|
+
.mockImplementationOnce(() => new Promise<Response>((resolve) => { finishWrite = resolve; }))
|
|
108
|
+
.mockResolvedValueOnce(json({ name: "old" }))
|
|
109
|
+
.mockResolvedValueOnce(json({ name: "new" }));
|
|
110
|
+
vi.stubGlobal("fetch", fetch);
|
|
111
|
+
const write = api.profile.update({ name: "new" });
|
|
112
|
+
await expect(api.profile.get({ startup: true })).resolves.toEqual({ name: "old" });
|
|
113
|
+
finishWrite(json({ name: "new" }));
|
|
114
|
+
await write;
|
|
115
|
+
await expect(api.profile.get({ startup: true })).resolves.toEqual({ name: "new" });
|
|
116
|
+
expect(fetch).toHaveBeenCalledTimes(3);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("bounds retained data and separates thread cursor requests", async () => {
|
|
120
|
+
const { api } = await import("./client");
|
|
121
|
+
const fetch = vi.fn().mockImplementation(() => Promise.resolve(json({ messages: [] })));
|
|
122
|
+
vi.stubGlobal("fetch", fetch);
|
|
123
|
+
for (let i = 0; i < 17; i++) await api.threads.get(`thread-${i}`, { startup: true });
|
|
124
|
+
await api.threads.get("thread-0", { startup: true });
|
|
125
|
+
expect(fetch).toHaveBeenCalledTimes(18);
|
|
126
|
+
await api.threads.get("thread-0", { after: 10 });
|
|
127
|
+
expect(fetch.mock.calls[18][0]).toBe("/api/v1/threads/thread-0?after=10");
|
|
128
|
+
});
|
|
129
|
+
});
|
package/api/client.ts
CHANGED
|
@@ -61,6 +61,7 @@ import type {
|
|
|
61
61
|
ToolConfigSlotInfo,
|
|
62
62
|
TaskAssignment,
|
|
63
63
|
ThreadDetail,
|
|
64
|
+
ThreadMetadata,
|
|
64
65
|
ThreadSummary,
|
|
65
66
|
UserProfile,
|
|
66
67
|
BuiltinToolCategoryInfo,
|
|
@@ -369,11 +370,41 @@ async function parseJsonResponse<T>(res: Response): Promise<T> {
|
|
|
369
370
|
return JSON.parse(await new Response(stream).text()) as T;
|
|
370
371
|
}
|
|
371
372
|
|
|
372
|
-
|
|
373
|
+
const STARTUP_TTL_MS = 5_000;
|
|
374
|
+
const startupReads = new Map<string, { promise: Promise<unknown>; settledAt: number | null }>();
|
|
375
|
+
|
|
376
|
+
function startupRead<T>(path: string): Promise<T> {
|
|
377
|
+
const now = Date.now();
|
|
378
|
+
for (const [key, entry] of startupReads) {
|
|
379
|
+
if (entry.settledAt !== null && (now < entry.settledAt || now - entry.settledAt >= STARTUP_TTL_MS)) {
|
|
380
|
+
startupReads.delete(key);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const cached = startupReads.get(path);
|
|
384
|
+
if (cached) return cached.promise.then((value) => structuredClone(value as T));
|
|
385
|
+
// Bound retained startup data when agents are switched rapidly.
|
|
386
|
+
const oldestKey = startupReads.keys().next().value;
|
|
387
|
+
if (startupReads.size >= 16 && oldestKey !== undefined) startupReads.delete(oldestKey);
|
|
388
|
+
const entry = { promise: Promise.resolve<unknown>(undefined), settledAt: null as number | null };
|
|
389
|
+
entry.promise = request<T>(path, { startup: true }).then((value) => {
|
|
390
|
+
entry.settledAt = Date.now();
|
|
391
|
+
return value;
|
|
392
|
+
}).catch((error: unknown) => {
|
|
393
|
+
if (startupReads.get(path) === entry) startupReads.delete(path);
|
|
394
|
+
throw error;
|
|
395
|
+
});
|
|
396
|
+
startupReads.set(path, entry);
|
|
397
|
+
return entry.promise.then((value) => structuredClone(value as T));
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
async function request<T>(path: string, init?: RequestInit & { timeoutMs?: number; startup?: boolean }): Promise<T> {
|
|
401
|
+
const mutation = !!init?.method && init.method !== "GET";
|
|
402
|
+
if (mutation) startupReads.clear();
|
|
403
|
+
else if (!init?.startup) startupReads.delete(path);
|
|
373
404
|
const cfg = runtimeConfig();
|
|
374
405
|
const maxAttempts = Math.max(1, cfg.httpMaxAttempts);
|
|
375
406
|
const timeoutMs = init?.timeoutMs ?? cfg.httpRequestTimeoutMs;
|
|
376
|
-
const { timeoutMs: _ignoreTimeoutMs, ...fetchInit } = init ?? {};
|
|
407
|
+
const { timeoutMs: _ignoreTimeoutMs, startup: _ignoreStartup, ...fetchInit } = init ?? {};
|
|
377
408
|
const callerSignal = fetchInit.signal ?? null;
|
|
378
409
|
|
|
379
410
|
let lastErr: unknown = null;
|
|
@@ -434,6 +465,7 @@ async function request<T>(path: string, init?: RequestInit & { timeoutMs?: numbe
|
|
|
434
465
|
if (attempt < maxAttempts && isRetryable(err)) continue;
|
|
435
466
|
throw err;
|
|
436
467
|
} finally {
|
|
468
|
+
if (mutation) startupReads.clear();
|
|
437
469
|
clearTimeout(timer);
|
|
438
470
|
callerSignal?.removeEventListener("abort", onCallerAbort);
|
|
439
471
|
}
|
|
@@ -446,7 +478,10 @@ export const api = {
|
|
|
446
478
|
agents: {
|
|
447
479
|
list: (opts?: { force?: boolean }) =>
|
|
448
480
|
cachedList(agentListCache, () => request<AgentConfig[]>("/agents"), setAgentListCache, opts?.force === true),
|
|
449
|
-
get: (id: string) =>
|
|
481
|
+
get: (id: string, opts?: { startup?: boolean }) => {
|
|
482
|
+
const path = `/agents/${encodeURIComponent(id)}`;
|
|
483
|
+
return opts?.startup ? startupRead<AgentConfig>(path) : request<AgentConfig>(path);
|
|
484
|
+
},
|
|
450
485
|
create: async (data: AgentConfigIn) => {
|
|
451
486
|
const created = await request<AgentConfig>("/agents", { method: "POST", body: JSON.stringify(data) });
|
|
452
487
|
if (agentListCache.data) setAgentListCache([...agentListCache.data, created]);
|
|
@@ -467,8 +502,10 @@ export const api = {
|
|
|
467
502
|
}
|
|
468
503
|
return res;
|
|
469
504
|
},
|
|
470
|
-
getThread: (id: string) =>
|
|
471
|
-
|
|
505
|
+
getThread: (id: string, opts?: { startup?: boolean }) => {
|
|
506
|
+
const path = `/agents/${encodeURIComponent(id)}/thread`;
|
|
507
|
+
return opts?.startup ? startupRead<ThreadSummary>(path) : request<ThreadSummary>(path);
|
|
508
|
+
},
|
|
472
509
|
compact: (id: string, options?: { resetContext?: boolean }) =>
|
|
473
510
|
request<{
|
|
474
511
|
compacted: boolean;
|
|
@@ -705,7 +742,7 @@ export const api = {
|
|
|
705
742
|
list: (limit = 50, offset = 0) => request<ThreadSummary[]>(`/threads?limit=${limit}&offset=${offset}`),
|
|
706
743
|
create: (agent_id: string, title?: string) =>
|
|
707
744
|
request<ThreadSummary>("/threads", { method: "POST", body: JSON.stringify({ agent_id, title }) }),
|
|
708
|
-
get: (thread_id: string, opts?: { limit?: number; before?: number; after?: number }) => {
|
|
745
|
+
get: (thread_id: string, opts?: { limit?: number; before?: number; after?: number; startup?: boolean }) => {
|
|
709
746
|
const p = new URLSearchParams();
|
|
710
747
|
if (opts?.limit) p.set("limit", String(opts.limit));
|
|
711
748
|
if (opts?.before !== undefined) p.set("before", String(opts.before));
|
|
@@ -714,8 +751,11 @@ export const api = {
|
|
|
714
751
|
// user+assistant pair instead of re-pulling the whole most-recent page.
|
|
715
752
|
if (opts?.after !== undefined) p.set("after", String(opts.after));
|
|
716
753
|
const qs = p.toString() ? `?${p}` : "";
|
|
717
|
-
|
|
754
|
+
const path = `/threads/${thread_id}${qs}`;
|
|
755
|
+
return opts?.startup ? startupRead<ThreadDetail>(path) : request<ThreadDetail>(path);
|
|
718
756
|
},
|
|
757
|
+
metadata: (thread_id: string, signal?: AbortSignal) =>
|
|
758
|
+
request<ThreadMetadata>(`/threads/${thread_id}?view=metadata`, { signal }),
|
|
719
759
|
delete: (thread_id: string) =>
|
|
720
760
|
request<{ deleted: boolean }>(`/threads/${thread_id}`, { method: "DELETE" }),
|
|
721
761
|
// What the thread is doing now (run phase, queued turns, compaction).
|
|
@@ -893,7 +933,8 @@ export const api = {
|
|
|
893
933
|
},
|
|
894
934
|
|
|
895
935
|
profile: {
|
|
896
|
-
get: () =>
|
|
936
|
+
get: (opts?: { startup?: boolean }) =>
|
|
937
|
+
opts?.startup ? startupRead<UserProfile>("/profile") : request<UserProfile>("/profile"),
|
|
897
938
|
update: (data: Partial<Pick<UserProfile, "name" | "icon" | "about" | "preset">>) =>
|
|
898
939
|
request<UserProfile>("/profile", { method: "PUT", body: JSON.stringify(data) }),
|
|
899
940
|
setLocationConsent: (consent: boolean) =>
|
|
@@ -1397,6 +1438,7 @@ export async function submitRun(
|
|
|
1397
1438
|
channels?: string[],
|
|
1398
1439
|
hot_since_seq?: number | null,
|
|
1399
1440
|
): Promise<SubmitResult> {
|
|
1441
|
+
startupReads.clear();
|
|
1400
1442
|
const payload: {
|
|
1401
1443
|
message: string;
|
|
1402
1444
|
stream_options?: StreamOptions;
|
|
@@ -1413,7 +1455,7 @@ export async function submitRun(
|
|
|
1413
1455
|
headers: { "Content-Type": "application/json" },
|
|
1414
1456
|
body: JSON.stringify(runPayload),
|
|
1415
1457
|
signal,
|
|
1416
|
-
});
|
|
1458
|
+
}).finally(() => startupReads.clear());
|
|
1417
1459
|
// 2xx = accepted (currently always 202); 409 = already running. We treat
|
|
1418
1460
|
// every other status as a hard error so the consumer's catch fires.
|
|
1419
1461
|
if (res.status === 202) {
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { NextRequest } from "next/server";
|
|
3
|
+
|
|
4
|
+
const mocks = vi.hoisted(() => ({
|
|
5
|
+
getThread: vi.fn(), getMessagesPage: vi.fn(), getMessagesAfter: vi.fn(),
|
|
6
|
+
getMessageUsageByIds: vi.fn(), getAgentConfig: vi.fn(),
|
|
7
|
+
}));
|
|
8
|
+
vi.mock("@/lib/stores/threads", () => ({ ...mocks, deleteThread: vi.fn() }));
|
|
9
|
+
vi.mock("@/lib/stores/message-usage", () => ({ getMessageUsageByIds: mocks.getMessageUsageByIds }));
|
|
10
|
+
vi.mock("@/lib/stores/agent-configs", () => ({ getAgentConfig: mocks.getAgentConfig }));
|
|
11
|
+
vi.mock("@/lib/stores/model-config", () => ({ getModelConfig: vi.fn(), getModelParams: vi.fn() }));
|
|
12
|
+
vi.mock("@/lib/agents/warm-summary-background", () => ({
|
|
13
|
+
currentChannelSummaries: () => [], isManualCompactionRunning: () => true,
|
|
14
|
+
pendingCompactionBoundary: () => null, pendingCompactionBoundarySeq: () => 10,
|
|
15
|
+
}));
|
|
16
|
+
vi.mock("@/lib/agents/conversation-summary", () => ({ parseStoredTopics: () => null }));
|
|
17
|
+
vi.mock("@/lib/agents/thread-activity", () => ({ getThreadActivity: () => ({ busy: true }) }));
|
|
18
|
+
vi.mock("@/lib/agents/checkpointer", () => ({ getCheckpointer: vi.fn() }));
|
|
19
|
+
|
|
20
|
+
const { GET } = await import("@/app/api/v1/threads/[thread_id]/route");
|
|
21
|
+
function get(query = "") {
|
|
22
|
+
return GET(new NextRequest(`http://localhost/api/v1/threads/t1${query}`),
|
|
23
|
+
{ params: Promise.resolve({ thread_id: "t1" }) });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe("thread metadata view", () => {
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
vi.clearAllMocks();
|
|
29
|
+
mocks.getThread.mockReturnValue({ thread_id: "t1", agent_id: "a1", hot_since_seq: 4 });
|
|
30
|
+
mocks.getMessagesPage.mockReturnValue({ messages: [], has_more: true });
|
|
31
|
+
mocks.getMessagesAfter.mockReturnValue([]);
|
|
32
|
+
mocks.getMessageUsageByIds.mockReturnValue(new Map());
|
|
33
|
+
mocks.getAgentConfig.mockReturnValue(null);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("returns live metadata without reading messages or usage", async () => {
|
|
37
|
+
const response = await get("?view=metadata");
|
|
38
|
+
const data = await response.json();
|
|
39
|
+
expect(data).toMatchObject({
|
|
40
|
+
thread_id: "t1", hot_since_seq: 4, pending_hot_since_seq: 10,
|
|
41
|
+
manual_compaction_pending: true, activity: { busy: true }, context_window_tokens: 8192,
|
|
42
|
+
});
|
|
43
|
+
expect(data).not.toHaveProperty("messages");
|
|
44
|
+
expect(data).not.toHaveProperty("has_more");
|
|
45
|
+
expect(response.headers.get("Cache-Control")).toBe("no-store");
|
|
46
|
+
expect(mocks.getMessagesPage).not.toHaveBeenCalled();
|
|
47
|
+
expect(mocks.getMessagesAfter).not.toHaveBeenCalled();
|
|
48
|
+
expect(mocks.getMessageUsageByIds).not.toHaveBeenCalled();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("preserves the default paged response and before cursor", async () => {
|
|
52
|
+
const response = await get("?before=42&limit=10");
|
|
53
|
+
expect(await response.json()).toMatchObject({ messages: [], has_more: true });
|
|
54
|
+
expect(mocks.getMessagesPage).toHaveBeenCalledWith("t1", 10, 42);
|
|
55
|
+
expect(mocks.getMessageUsageByIds).toHaveBeenCalledWith([]);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("preserves forward message reads", async () => {
|
|
59
|
+
expect(await (await get("?after=42&limit=10")).json()).toMatchObject({ messages: [], has_more: false });
|
|
60
|
+
expect(mocks.getMessagesAfter).toHaveBeenCalledWith("t1", 42, 10);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("rejects unknown views", async () => {
|
|
64
|
+
expect((await get("?view=unknown")).status).toBe(400);
|
|
65
|
+
expect(mocks.getMessagesPage).not.toHaveBeenCalled();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("preserves missing-thread responses", async () => {
|
|
69
|
+
mocks.getThread.mockReturnValue(null);
|
|
70
|
+
expect((await get("?view=metadata")).status).toBe(404);
|
|
71
|
+
});
|
|
72
|
+
});
|
package/api/types.ts
CHANGED
|
@@ -378,9 +378,7 @@ export interface MessageUsage {
|
|
|
378
378
|
cost_usd: number | null;
|
|
379
379
|
}
|
|
380
380
|
|
|
381
|
-
export interface
|
|
382
|
-
messages: Message[];
|
|
383
|
-
has_more: boolean;
|
|
381
|
+
export interface ThreadMetadata extends ThreadSummary {
|
|
384
382
|
// Effective context-window size (in tokens) for this thread's current
|
|
385
383
|
// agent + model config. Used by the chat UI to scale the per-turn
|
|
386
384
|
// context-usage bar against the same cap the agent applies at run time.
|
|
@@ -416,6 +414,11 @@ export interface ThreadDetail extends ThreadSummary {
|
|
|
416
414
|
warm_channel_summaries?: WarmChannelSummary[];
|
|
417
415
|
}
|
|
418
416
|
|
|
417
|
+
export interface ThreadDetail extends ThreadMetadata {
|
|
418
|
+
messages: Message[];
|
|
419
|
+
has_more: boolean;
|
|
420
|
+
}
|
|
421
|
+
|
|
419
422
|
/** What a thread is doing right now — GET /threads/:id/activity. */
|
|
420
423
|
export interface ThreadActivity {
|
|
421
424
|
run: { phase: string; label: string; started_at: number; agent_id: string | null } | null;
|