feat: 277 session and memory visibility across Corvus clients#366
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds SQLite-backed session lifecycle and admin memory visibility: new session types and Memory trait APIs, SQLite sessions table and hygiene, gateway admin and end-user session/memory endpoints plus session tracking in webhook/stream, and dashboard/chat frontend UI, composables, types, and tests to surface sessions and admin memory browsing/deletion. Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Chat Client
participant Gateway as Gateway
participant SQLite as SQLite Memory
participant Auth as Auth/TokenHash
Client->>Gateway: POST /web/chat/stream (session_id, Authorization)
activate Gateway
Gateway->>Auth: extract Bearer token, compute_token_hash()
Auth-->>Gateway: token_hash
Gateway->>SQLite: upsert_session(session_id, token_hash) // best-effort/auth-gated
SQLite-->>Gateway: ok / err
Gateway->>SQLite: update_session_activity(session_id) // best-effort
SQLite-->>Gateway: ok / err
Gateway-->>Client: stream response
deactivate Gateway
Client->>Gateway: GET /session/list (Authorization)
activate Gateway
Gateway->>Auth: extract Bearer token, compute_token_hash()
Auth-->>Gateway: token_hash
Gateway->>SQLite: list_sessions_for_token(token_hash, limit, offset)
SQLite-->>Gateway: sessions + total
Gateway-->>Client: { sessions, total }
deactivate Gateway
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
✅ Contributor ReportUser: @yacosta738
Contributor Report evaluates based on public GitHub activity. Analysis period: 2025-03-29 to 2026-03-29 |
There was a problem hiding this comment.
Actionable comments posted: 39
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
clients/web/apps/chat/src/App.spec.ts (1)
142-157: 🧹 Nitpick | 🔵 TrivialReduce brittleness from index-based fetch assertions
These assertions now depend on exact call ordering after background session-list fetches. Prefer selecting calls by URL/method so future polling or prefetch changes don’t cause unrelated test failures.
🛠️ Suggested refactor pattern
-const [streamUrl, streamInit] = fetchMock.mock.calls[5] ?? []; +const streamCall = fetchMock.mock.calls.find(([url]) => + String(url).includes("/web/chat/stream") +); +const [streamUrl, streamInit] = streamCall ?? []; -const [webhookUrl, webhookInit] = fetchMock.mock.calls[6] ?? []; +const webhookCall = fetchMock.mock.calls.find(([url]) => + String(url).includes("/webhook") +); +const [webhookUrl, webhookInit] = webhookCall ?? [];🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@clients/web/apps/chat/src/App.spec.ts` around lines 142 - 157, The test currently assumes exact fetch call ordering (using fetchMock.mock.calls[1], [5], [6]) which is brittle; update the assertions to locate calls by their request URL instead: search fetchMock.mock.calls for the entry whose URL string contains "/pair" to extract pairUrl/pairInit and assert the "X-Pairing-Code" header, similarly find the call with "/web/chat/stream" to assert Authorization and "X-Session-Id", and find the call with "/webhook" to assert the webhook URL—replace the index-based lookups with these URL-based finds so the test no longer depends on call ordering.clients/web/apps/chat/src/App.vue (1)
257-266:⚠️ Potential issue | 🟠 MajorReset the transcript when a session has no cached local history.
restoreMessages()returns early on a cache miss or parse failure, so afterswitchSession()/resumeSession()the old session’s messages stay on screen under the newcurrentSessionId. That leaks one session’s transcript into another session view. Clear to an empty or welcome state before attempting restore, and only replace it when the cached payload is valid.Patch idea
function restoreMessages(): void { + resetMessagesForSession(); + messageIdCounter = 1; if (!chat.currentSessionId.value) return; try { const raw = sessionStorage.getItem(messagesStorageKey()); if (!raw) return; const parsed = JSON.parse(raw) as Message[]; if (Array.isArray(parsed) && parsed.length > 0) { messages.value = parsed; - messageIdCounter = Math.max(...parsed.map((m) => m.id)) + 1; + messageIdCounter = Math.max(0, ...parsed.map((m) => m.id)) + 1; } } catch { // Ignore parse failures. } }As per coding guidelines, "Security first, performance second."
Also applies to: 313-317
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@clients/web/apps/chat/src/App.vue` around lines 257 - 266, restoreMessages() currently returns early on cache miss/parse failure leaving the previous session's messages visible when switchSession()/resumeSession() changes currentSessionId; update restoreMessages() to first reset messages to an empty/welcome state (e.g., set messages.value = [] and reset messageIdCounter appropriately) before attempting to read sessionStorage via messagesStorageKey(), then only assign parsed payload to messages.value and update messageIdCounter when parsed is a valid non-empty array; ensure the same reset logic is applied to the other call site mentioned (lines around 313-317) so stale transcripts cannot leak across sessions.clients/web/apps/dashboard/src/App.vue (1)
426-438:⚠️ Potential issue | 🟡 MinorWebhook status section appears on all pages.
This section renders regardless of
currentPagevalue, which may be unintentional. If it should only appear on the Config page, move it inside thev-if="currentPage === 'config'"template block.Proposed fix if intentional Config-only display
+ <!-- Inside the config template block, before </template> --> + <section class="card"> + <p class="helper"> + {{ + t("webhook.secretStatus", { + status: config.form.webhook_secret_exists + ? t("webhook.statusConfigured") + : t("webhook.statusNotConfigured"), + }) + }} + </p> + <p v-if="config.statusMessage.value" class="ok">{{ config.statusMessage.value }}</p> + <p v-if="config.errorMessage.value" class="error">{{ config.errorMessage.value }}</p> + </section> - - <section class="card"> - <p class="helper"> - {{ - t("webhook.secretStatus", { - status: config.form.webhook_secret_exists - ? t("webhook.statusConfigured") - : t("webhook.statusNotConfigured"), - }) - }} - </p> - <p v-if="config.statusMessage.value" class="ok">{{ config.statusMessage.value }}</p> - <p v-if="config.errorMessage.value" class="error">{{ config.errorMessage.value }}</p> - </section>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@clients/web/apps/dashboard/src/App.vue` around lines 426 - 438, The webhook status card is rendered unconditionally; if it should only appear on the Config page, move the <section class="card"> that reads config.form.webhook_secret_exists and the two conditional paragraphs (config.statusMessage.value and config.errorMessage.value) inside the template block guarded by v-if="currentPage === 'config'". Locate the card using the config references (config.form.webhook_secret_exists, config.statusMessage, config.errorMessage) and wrap it with the existing conditional that checks currentPage (currentPage === 'config') so it only renders on the Config page.clients/agent-runtime/src/gateway/mod.rs (2)
1735-1740:⚠️ Potential issue | 🟠 MajorTouch session activity before the legacy early return.
The
canonical_outcome_early_response()branch returns beforeupdate_session_activity(), so approval-required, timeout-aborted, and fallback turns never refreshlast_activityor message counts. That will make stale-session hygiene and the new session views drift from real usage.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@clients/agent-runtime/src/gateway/mod.rs` around lines 1735 - 1740, The early-return branch that handles canonical_outcome_early_response currently returns before updating session activity, causing stale last_activity/message counts; before calling release_idempotency_key or returning the response, invoke update_session_activity (using the same state, session_id and any message metadata used elsewhere) so approval-required/timeout/fallback turns refresh last_activity and counts; keep the existing call to release_idempotency_key(reserved_idempotency_key, persist_idempotency) after the update and then return the response.
1675-1699:⚠️ Potential issue | 🟠 MajorReserve the idempotency key before creating or touching a session.
resolve_session_id()andupsert_session()run beforerecord_if_new(). A replayed request can therefore create/touch a session row—even a brand-new generated one—while the gateway still returns"status": "duplicate".As per coding guidelines "Security first, performance second. Validate input boundaries, auth/authz implications, and secret management."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@clients/agent-runtime/src/gateway/mod.rs` around lines 1675 - 1699, Move the idempotency reservation logic so the request is checked for duplicates before any session resolution or persistence occurs: call webhook_idempotency_key(&headers) and immediately call state.idempotency_store.record_if_new(idempotency_key), returning webhook_duplicate_response(idempotency_key) on false, before calling resolve_session_id(&headers) or state.mem.upsert_session(...). This ensures record_if_new runs first and prevents replayed requests from creating/touching sessions; update the code paths that currently reference resolve_session_id, upsert_session, record_if_new, webhook_idempotency_key, and webhook_duplicate_response accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@clients/agent-runtime/src/gateway/admin.rs`:
- Around line 2173-2209: The code sets total from entries.len() after calling
state.mem.recall(q, 200, session_filter) and then applying category filtering,
so total is capped/filtered and may be inaccurate for large search results; fix
by either 1) performing a separate accurate count query (e.g., call a dedicated
count method like state.mem.count_recall(q, session_filter, category) or another
API that returns the full match count) and assign that to total before
paging/filtering, or 2) if no count API exists or you prefer not to add one,
document that AdminMemoryListResponse.total is an approximate count for search
queries (update the handler doc/comments and/or set a boolean flag in the
response indicating "approximate_total"). Ensure changes reference params.q,
state.mem.recall, and AdminMemoryListResponse so the fix is applied in this
search branch.
- Around line 2220-2243: The current list path uses state.mem.list(...) to fetch
up to the backend's default batch and then sets total = entries.len() which can
undercount large stores; add a clear comment above the state.mem.list(...) call
and/or on AdminMemoryListResponse explaining that Memory::list currently returns
a capped batch and total is only the count of the fetched batch (not the full
store), and as a minimal code change make total conservative by computing total
= (offset as u64) + (paged.len() as u64) so the returned total never
under-represents items after the offset; reference state.mem.list, entries,
paged, and AdminMemoryListResponse when making this change and document the
limitation so callers know a true-count/count API is required for accurate
pagination.
In `@clients/agent-runtime/src/gateway/mod.rs`:
- Around line 1680-1688: The current code swallows failures from
mem.upsert_session (called after utils::extract_bearer_token and
compute_token_hash) and continues processing, which lets requests bypass session
ownership/persistence; change this to fail-closed: if
mem.upsert_session(&session_id, token_hash.as_deref()).await returns Err, return
an appropriate error response (or propagate the Err) instead of just logging
with tracing::debug, so the request is rejected and access/persistence
guarantees are enforced; apply the same change for the second occurrence around
the lines that reference upsert_session.
In `@clients/agent-runtime/src/memory/hygiene.rs`:
- Around line 359-360: Replace the two separate Utc::now() calls with a single
timestamp to avoid time drift: create one let now = Utc::now(); then compute
now.to_rfc3339() for the "now" string and (now -
Duration::hours(threshold_hours)).to_rfc3339() for cutoff; update the variables
now and cutoff in the hygiene logic that compares against ended_at so both use
the same instant.
In `@clients/agent-runtime/src/memory/sqlite.rs`:
- Around line 911-924: The SQL builds safe literals via sort_col and order_dir
and parameterizes status, but you should validate the status input before
constructing sql_query: add a guard around the `status` variable (the same one
used in the sql_query conditional) to only accept known values (e.g., "active",
"ended", or None) and return an early Err/validation error from the calling
trait method if it’s unexpected; locate the validation near the top of the
function that computes `sort_col`, `order_dir`, and `sql_query` and reject
invalid status values before proceeding to prepare/execute the query.
In `@clients/agent-runtime/src/memory/traits.rs`:
- Around line 33-43: The SessionEntry struct uses a freeform String for the
status field which should be replaced with a strongly typed enum to restrict
values to "active" and "ended": add a SessionStatus enum (derive Debug, Clone,
Copy, Serialize, Deserialize, PartialEq, Eq and use #[serde(rename_all =
"snake_case")]) and change SessionEntry::status from String to SessionStatus;
update any code that constructs, compares, or serializes/deserializes
SessionEntry (factory functions, tests, DB read/write, and comparisons) to use
SessionStatus::Active / SessionStatus::Ended and ensure serde compatibility with
existing persisted values (handle migration or provide custom deserialize if
needed).
In `@clients/web/apps/chat/src/components/SessionSidebar.spec.ts`:
- Around line 78-90: The test in SessionSidebar.spec.ts uses a fragile
text-based selector when looking for a session item; update the SessionSidebar
component to add a stable data-testid on each session element (e.g.,
data-testid="session-{session.id}") and then modify the test's mountSidebar
usage to locate the element by that attribute (use
wrapper.find('[data-testid="session-sess-2"]') or equivalent) instead of relying
on .find(item => item.text().includes("sess-2")), keeping the rest of the
assertions (emitted event checks for "switch-session" and payload "sess-2")
unchanged.
In `@clients/web/apps/chat/src/components/SessionSidebar.vue`:
- Around line 106-121: The session list is using role="list" with each session
rendered as a <button> having role="listitem", which is invalid ARIA; update the
markup so the container is a real list and each session is an <li> that contains
the clickable <button>. Specifically, change the element rendering
sortedSessions in the component so the wrapper uses a real list element and each
item is an <li> that wraps the button that calls emit('switch-session',
session.id); keep existing logic for currentSessionId, truncateId(session.id),
formatRelativeTime(session.last_activity) and t("session.messageCount", { count:
session.message_count }) but move the role/aria-current/class bindings to the
button and remove role="listitem" from the button.
In `@clients/web/apps/chat/src/composables/useChat.spec.ts`:
- Around line 518-535: The test must enqueue the /session/list mock response
before calling chat.createSession(), because createSession() flips state to
"session_ready" and triggers a watcher that calls chat.fetchSessionList(); move
the fetchMock.mockResolvedValueOnce(...) call so it runs prior to
chat.createSession() (and ensure the same change is applied to the other
occurrence that mocks the session list in the later test), so both the
watcher-driven fetch and the explicit await chat.fetchSessionList() consume
predictable mocked responses rather than racing for a single mock.
In `@clients/web/apps/chat/src/composables/useChat.ts`:
- Around line 535-546: fetchSessionList currently returns early when
gateway.isGatewayReady is false or silently ignores failures, leaving stale
sessions in sessionList; update fetchSessionList to explicitly clear
sessionList.value (and any related sidebar state such as selected/active session
if present) when readiness is false or when the try/catch fails, and also ensure
gateway readiness changes clear the same state (e.g., add a watcher on
gateway.isGatewayReady to reset sessionList.value and sidebar selection when it
becomes false) so the UI cannot show prior gateway/token sessions after logout
or credential changes.
- Around line 532-558: The interval started by startSessionPolling (which sets
sessionPollTimer) isn't cleared when the composable is disposed; import and use
Vue's onScopeDispose (or onUnmounted if appropriate) in the composable and call
stopSessionPolling() inside that disposal callback so the interval is cleared
when the composable scope unmounts; reference sessionPollTimer,
startSessionPolling, stopSessionPolling, and fetchSessionList when locating
where to add the onScopeDispose hook.
In `@clients/web/apps/chat/src/composables/useGateway.ts`:
- Around line 476-482: The current response handling incorrectly maps 401/403 to
an empty sessions result; change the conditional logic in useGateway.ts so that
only 404 (and other unsupported-runtime cases) return { sessions: [], total: 0
}, while 401 and 403 are handled like the send path (i.e., surface an
authentication error/trigger credential recovery instead of returning an empty
list). Locate the block that checks response.status and replace the combined
fallback with explicit checks for 401/403 to throw or propagate an auth-specific
error (or the same error-handling flow used by the send method), keeping the
empty-array fallback exclusively for 404/unsupported runtime responses.
- Around line 457-471: getSessionList treats offset as page-aligned which can
produce surprising results and accepts invalid limits; update the function
(getSessionList) to validate inputs before building the URL: require limit to be
a positive integer (>0) and either accept only offsets that are integers aligned
to limit (offset % limit === 0) or change the public API to accept (page,
perPage) instead of (limit, offset). If you keep (limit, offset), add checks
that Number.isInteger(limit) && limit > 0 && Number.isInteger(offset) && offset
>= 0 && offset % limit === 0 and throw a clear error when validation fails; if
you switch to (page, perPage), compute page directly and validate page >= 1 and
perPage > 0. Ensure the URL construction still uses
gatewayUrl(`/session/list?page=${page}&per_page=${limitOrPerPage}`) after
validation.
In `@clients/web/apps/chat/src/types/chat.ts`:
- Around line 22-27: The SessionListItem interface is missing the ended_at
field; update the SessionListItem type to include an ended_at property
(preferably typed as string | null to represent active vs ended sessions) so it
matches the session list contract and supports ended-session UI states—modify
the SessionListItem interface declaration to add ended_at: string | null and
update any callers/tests that construct or consume SessionListItem accordingly.
In `@clients/web/apps/dashboard/src/components/memory/MemoryFilters.vue`:
- Around line 19-37: The debounce timeout searchTimeout (used by onSearchInput)
isn't cleared when the component unmounts; add an onUnmounted hook that calls
clearTimeout(searchTimeout) and sets searchTimeout = undefined to prevent the
pending timer from firing after teardown, and import onUnmounted from 'vue' if
necessary; locate the top-level searchTimeout declaration and the onSearchInput
function to add this cleanup logic.
In `@clients/web/apps/dashboard/src/components/memory/MemoryList.spec.ts`:
- Around line 76-77: Replace uses of vi.dynamicImportSettled() in
MemoryList.spec.ts with flushPromises() from `@vue/test-utils` because the
component triggers mocked fetches on mount (see MemoryList.vue) and
dynamicImportSettled only waits for imports; import flushPromises and call it
after mounting the component and after any user interactions that trigger
fetches (the instances to change are the calls currently using
vi.dynamicImportSettled at/around lines referenced), ensuring assertions on
table rows and pagination run after pending promise resolution and Vue updates.
In `@clients/web/apps/dashboard/src/components/memory/MemoryList.vue`:
- Around line 49-55: After a successful delete in confirmDelete(), clamp the
current page value so it never exceeds totalPages() before calling load();
specifically, after admin.deleteMemoryEntry(confirmingDelete.value) returns true
and before await load(), set the page reactive variable to Math.min(page.value,
totalPages()) (and ensure it stays at least 1 if your pager is 1-based) so the
UI won't render an empty table or stale "page / totalPages" indicator.
- Around line 62-67: The current watcher on [() => props.categoryFilter, () =>
props.sessionIdFilter, () => props.searchFilter] both sets page.value = 1 and
calls load(), which duplicates the fetch because the separate watch(page) also
calls load()—remove the double request by changing the filter watcher to only
set page.value = 1 (and only call load() if page is already 1); update the
filter watcher logic in MemoryList.vue so it does: if (page.value === 1) load();
else page.value = 1; this uses the existing watch(page, () => load()) to perform
the fetch when page is changed and prevents overlapping requests from load() and
the page watcher.
In `@clients/web/apps/dashboard/src/components/memory/MemoryStats.vue`:
- Around line 19-25: The template is using non-idiomatic `.value` access on refs
(admin.loading.value, admin.error.value, admin.memoryStats.value); update the
template in MemoryStats.vue to use the unwrapped refs (admin.loading,
admin.error, admin.memoryStats) so it reads the reactive values idiomatically
while keeping the same conditional logic and ARIA attributes.
In `@clients/web/apps/dashboard/src/components/sessions/SessionList.spec.ts`:
- Around line 71-72: Replace usages of vi.dynamicImportSettled() with await
flushPromises() and import flushPromises from '@vue/test-utils' so tests
properly wait for async fetches and Vue re-renders; specifically, in
SessionList.spec.ts replace each await vi.dynamicImportSettled() (occurrences
around lines with mounts and post-action assertions) with await flushPromises()
and ensure the import statement for flushPromises is added at the top and any
vi.dynamicImportSettled() imports/usages are removed.
In `@clients/web/apps/dashboard/src/components/sessions/SessionList.vue`:
- Around line 42-49: The current watcher on [() => props.statusFilter, () =>
props.sort] sets page.value = 1 and calls load(), which then triggers the
separate watch(page, ...) and causes a double fetch; change the array watcher to
only set page.value = 1 (remove the load() call) so the existing watch(page, ()
=> load()) performs the single fetch, or alternatively consolidate into one
watcher that handles resetting page and calling load() together (referencing
props.statusFilter, props.sort, page, and load).
In `@clients/web/apps/dashboard/src/composables/useAdmin.ts`:
- Around line 62-66: The fetch call that assigns const res = await fetch(url, {
headers: authHeaders() }) can hang indefinitely; update the request in useAdmin
(where PaginatedResponse<AdminSessionView> is parsed) to use an AbortController
with a configurable timeout (e.g., 30s): create the controller, pass
controller.signal into fetch, set a timeoutId that calls controller.abort(), and
always clearTimeout(timeoutId) in a finally block; also catch an aborted fetch
(AbortError) and throw a clear, user-friendly timeout error before proceeding to
check res.ok and parse res.json().
- Around line 39-49: The buildUrl function currently calls new
URL(gatewayUrl(path)) which will throw if gatewayUrl(path) returns a malformed
string; update buildUrl to validate or wrap the URL construction in a try-catch
around new URL(gatewayUrl(path)) so you can either return a safe fallback (e.g.,
empty string or null) or rethrow a clearer error with context including the path
and the invalid URL; reference the buildUrl and gatewayUrl symbols and ensure
any callers that expect a string handle the new fallback/exception behavior.
- Around line 141-157: The deleteMemoryEntry function does not set the shared
loading state, causing inconsistent UI behavior; update deleteMemoryEntry to set
loading.value = true before the fetch and ensure loading.value = false in a
finally block (similar to other fetch helpers) so loading is always cleared;
locate the async function deleteMemoryEntry and add the loading.value
assignments around the try/catch/finally to mirror the pattern used by the other
API methods in this file.
- Around line 34-37: The shared loading ref causes races; replace it with
per-operation state (e.g., loadingSessions and loadingMemory refs) or a single
Set/Map like loadingOps that tracks operation names, then update the
fetchSessions and fetchMemoryStats functions to add their operation to
loadingOps at start and remove it on finally (or set their specific loading ref
true/false), and update any consumers of loading (UI bindings or computed
properties) to read the new per-operation flags or whether loadingOps.size > 0;
locate the original const loading = ref(false) and the
fetchSessions/fetchMemoryStats implementations to make these changes.
In `@openspec/changes/archive/2026-03-28-session-memory-visibility/design.md`:
- Around line 393-396: The GET `/session/list` response currently reuses the
internal SessionEntry type (which includes status/metadata) — instead create and
return a minimal public DTO (e.g. SessionListItem or SessionSummary) containing
only fields allowed by the end-user contract (id, created_at, display_name,
etc.), update the handler that constructs the list for GET `/session/list` to
map/internal SessionEntry -> SessionListItem instead of serializing SessionEntry
directly, and update any response type/interface (SessionListResponse) to
reference this minimal DTO so internal fields like status/metadata are not
exposed.
- Around line 103-242: The markdown in design.md has multiple fenced code blocks
containing ASCII diagrams (e.g., the directory tree under "dashboard/src/", the
flow diagrams in "3. Data Flow" and subsections like "Session Lifecycle Flow",
"Admin Session/Memory Query Flow", "End-User Session List Flow") without a
language tag, triggering MD040; update each of those triple-backtick fences to
use ```text (not ``` or any other language) so the linter accepts ASCII
diagrams—ensure you change the opening fence only (leave the closing ``` intact)
across all diagram blocks in the file.
In
`@openspec/changes/archive/2026-03-28-session-memory-visibility/exploration.md`:
- Around line 47-50: Insert a single blank line between the preceding paragraph
and the "Existing Endpoints" table so the table is separated from the prior
text; update the block that begins with the "Existing Endpoints" heading (and
the table referencing `mod.rs` router) to have an empty line immediately before
the table markdown to satisfy MD058.
In `@openspec/changes/archive/2026-03-28-session-memory-visibility/proposal.md`:
- Around line 57-63: Update the archived proposal document to match the
implementation: replace the incorrect type file reference
`clients/web/apps/dashboard/src/types/admin-config.ts` with the actual
`clients/web/apps/dashboard/src/types/admin-sessions.ts`, and change the
session-detail behavior description from "returns memory entries" to state it
returns `memory_summary` (with the memory list endpoint used for browsing
entries); ensure the same corrections are applied to the other occurrences of
this text in the document that mirror the same list/description (the rows
mentioning session detail and the types in the diff).
In
`@openspec/changes/archive/2026-03-28-session-memory-visibility/specs/memory-visibility/spec.md`:
- Line 27: The spec currently documents the full-text query parameter as `q`,
but the implementation in useAdmin.ts expects `search`; update the spec to use
`search` everywhere the query parameter is defined (replace `q` with `search`)
and ensure any examples or references in memory-visibility/spec.md are
consistent, or alternatively change useAdmin.ts to accept `q` instead—pick one
and make all references (`q` vs `search`) consistent between the spec and the
useAdmin.ts handler.
- Around line 348-357: The bug is a field-name mismatch between the API contract
PaginatedResponse<T> (which exposes results as the data array on the data field)
and the client in useAdmin.ts that expects data.items; update useAdmin.ts to
read the array from the spec-defined field (i.e., use the
PaginatedResponse<T>.data payload) wherever it currently references data.items
(both occurrences in useAdmin.ts) so the client accesses response.data.data (or
otherwise map response.data to an items variable) instead of data.items.
In
`@openspec/changes/archive/2026-03-28-session-memory-visibility/specs/sessions/spec.md`:
- Around line 126-128: The fenced code block containing the ASCII state
transition diagram (`[active] ──(explicit close / auto-close)──▶ [ended]`) lacks
a language identifier and triggers markdownlint MD040; update the fence to
include a plain text language (e.g., change the opening ``` to ```text or
```plaintext) so the diagram is treated as text and the linter warning is
resolved.
In `@openspec/changes/archive/2026-03-28-session-memory-visibility/tasks.md`:
- Around line 534-545: The fenced code block containing the dependency-order
diagram starting with "Phase 1 (1.1 → 1.2 → 1.3 → 1.4, 1.5 parallel → 1.6)"
should include a language tag to satisfy MD040; change the opening triple
backticks to ```text so the block becomes a text code fence and the linter stops
flagging it.
- Around line 509-517: Add the missing rustfmt validation to the final check
steps: update the task that currently lists `cargo test` and `cargo clippy` for
the agent-runtime to also run `cargo fmt --all -- --check --manifest-path
clients/agent-runtime/Cargo.toml` (or, if you intentionally skip formatting, add
an explicit comment line documenting why the `cargo fmt` check for
clients/agent-runtime/**/*.rs is omitted), ensuring the README/task list
reflects the coding guidelines requiring `cargo fmt`, `cargo clippy`, and `cargo
test`.
- Line 93: The current approach truncates the SHA-256 output to 16 hex chars for
token_hash, reducing collision resistance; replace the truncated value by
computing and storing the full SHA-256 hex digest of the bearer token (or
better, compute HMAC-SHA-256 using a server secret) wherever token_hash is
derived (search for token_hash and any bearer token -> token_hash logic,
including the other occurrences mentioned around the second instance), update
related comments/field length expectations, and ensure any verification or
lookup code that reads token_hash accepts the full-length digest/HMAC.
In
`@openspec/changes/archive/2026-03-28-session-memory-visibility/verify-report.md`:
- Around line 34-40: The report incorrectly archives the run as "PASS WITH
WARNINGS" despite `cargo clippy -D warnings` failing and chat Vitest showing 5
failing tests; update verify-report.md to mark the overall verification as
FAILED (not pass-with-warnings), include the Clippy error text (`binding's name
is same as existing binding` for used_underscore_binding) and the note that chat
Vitest has 5 failing tests, and make the same corrections for the other affected
sections (around the blocks referenced at lines 59-64 and 210-214) so the
document accurately reflects the failing validation state; additionally, fix the
underlying Clippy complaint in the codebase by renaming the binding in the
gateway test function used_underscore_binding to avoid reusing an existing name
(removing the conflicting leading underscore usage) so the Clippy check can
pass.
In `@openspec/config.yaml`:
- Around line 29-31: The verify section currently lists aspirational steps;
replace the high-level bullets under the verify key with executable commands
that actually run tests: add explicit commands for each supported stack (e.g.,
run `cargo test` for Rust, the Gradle test task for JVM, and `pnpm test` for
JS/TS), or implement path-scoped commands that invoke the appropriate test
runner only for files changed in the PR; update the verify entry to reference
those concrete commands (e.g., Cargo/Gradle/pnpm invocations or a script that
maps changed paths to the correct test command) so the CI will execute tests
rather than merely listing intent.
In `@openspec/specs/memory-visibility/spec.md`:
- Around line 264-273: The scenario "Admin session detail includes memory
summary but not raw content" conflicts with the implemented admin inspection
model; update the Gherkin to match the admin contract by either (A) changing the
expectation for GET /web/admin/sessions/abc-123 to include full memory details
if the implementation allows inline content, or (B) if the implementation
requires separation, keep the session response limited to memory_summary and
explicitly reference GET /web/admin/memory?session_id=abc-123 as the required
way to retrieve full memory content; ensure the scenario text and the
THEN/SHOULD lines reference the correct behavior for the implemented contract
(adjust the lines mentioning memory_summary, "SHOULD NOT include full memory
content inline", and the pointer to GET /web/admin/memory?session_id=abc-123
accordingly).
- Around line 351-370: The paginated response fields are inconsistent
(PaginatedResponse<T> defines data/limit/offset while other contracts use
entries/page/per_page); pick one canonical shape and make all types and
contracts match it: either update PaginatedResponse<T> to use
entries/page/per_page or change the other contracts (including AdminMemoryEntry
response shape and the GET /web/admin/memory contract) to use data/limit/offset,
then update any tests, typings, and consumers to reference the chosen symbols
(PaginatedResponse<T>, AdminMemoryEntry, and the memory list contract) so all
code uses the same pagination property names.
---
Outside diff comments:
In `@clients/agent-runtime/src/gateway/mod.rs`:
- Around line 1735-1740: The early-return branch that handles
canonical_outcome_early_response currently returns before updating session
activity, causing stale last_activity/message counts; before calling
release_idempotency_key or returning the response, invoke
update_session_activity (using the same state, session_id and any message
metadata used elsewhere) so approval-required/timeout/fallback turns refresh
last_activity and counts; keep the existing call to
release_idempotency_key(reserved_idempotency_key, persist_idempotency) after the
update and then return the response.
- Around line 1675-1699: Move the idempotency reservation logic so the request
is checked for duplicates before any session resolution or persistence occurs:
call webhook_idempotency_key(&headers) and immediately call
state.idempotency_store.record_if_new(idempotency_key), returning
webhook_duplicate_response(idempotency_key) on false, before calling
resolve_session_id(&headers) or state.mem.upsert_session(...). This ensures
record_if_new runs first and prevents replayed requests from creating/touching
sessions; update the code paths that currently reference resolve_session_id,
upsert_session, record_if_new, webhook_idempotency_key, and
webhook_duplicate_response accordingly.
In `@clients/web/apps/chat/src/App.spec.ts`:
- Around line 142-157: The test currently assumes exact fetch call ordering
(using fetchMock.mock.calls[1], [5], [6]) which is brittle; update the
assertions to locate calls by their request URL instead: search
fetchMock.mock.calls for the entry whose URL string contains "/pair" to extract
pairUrl/pairInit and assert the "X-Pairing-Code" header, similarly find the call
with "/web/chat/stream" to assert Authorization and "X-Session-Id", and find the
call with "/webhook" to assert the webhook URL—replace the index-based lookups
with these URL-based finds so the test no longer depends on call ordering.
In `@clients/web/apps/chat/src/App.vue`:
- Around line 257-266: restoreMessages() currently returns early on cache
miss/parse failure leaving the previous session's messages visible when
switchSession()/resumeSession() changes currentSessionId; update
restoreMessages() to first reset messages to an empty/welcome state (e.g., set
messages.value = [] and reset messageIdCounter appropriately) before attempting
to read sessionStorage via messagesStorageKey(), then only assign parsed payload
to messages.value and update messageIdCounter when parsed is a valid non-empty
array; ensure the same reset logic is applied to the other call site mentioned
(lines around 313-317) so stale transcripts cannot leak across sessions.
In `@clients/web/apps/dashboard/src/App.vue`:
- Around line 426-438: The webhook status card is rendered unconditionally; if
it should only appear on the Config page, move the <section class="card"> that
reads config.form.webhook_secret_exists and the two conditional paragraphs
(config.statusMessage.value and config.errorMessage.value) inside the template
block guarded by v-if="currentPage === 'config'". Locate the card using the
config references (config.form.webhook_secret_exists, config.statusMessage,
config.errorMessage) and wrap it with the existing conditional that checks
currentPage (currentPage === 'config') so it only renders on the Config page.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 29461c64-245e-425c-9176-0a987f058fc8
📒 Files selected for processing (43)
clients/agent-runtime/src/gateway/admin.rsclients/agent-runtime/src/gateway/mod.rsclients/agent-runtime/src/gateway/sessions.rsclients/agent-runtime/src/memory/hygiene.rsclients/agent-runtime/src/memory/mod.rsclients/agent-runtime/src/memory/sqlite.rsclients/agent-runtime/src/memory/traits.rsclients/web/apps/chat/src/App.spec.tsclients/web/apps/chat/src/App.vueclients/web/apps/chat/src/components/SessionSidebar.spec.tsclients/web/apps/chat/src/components/SessionSidebar.vueclients/web/apps/chat/src/composables/useChat.spec.tsclients/web/apps/chat/src/composables/useChat.tsclients/web/apps/chat/src/composables/useGateway.spec.tsclients/web/apps/chat/src/composables/useGateway.tsclients/web/apps/chat/src/types/chat.tsclients/web/apps/dashboard/src/App.vueclients/web/apps/dashboard/src/components/memory/MemoryFilters.vueclients/web/apps/dashboard/src/components/memory/MemoryList.spec.tsclients/web/apps/dashboard/src/components/memory/MemoryList.vueclients/web/apps/dashboard/src/components/memory/MemoryStats.spec.tsclients/web/apps/dashboard/src/components/memory/MemoryStats.vueclients/web/apps/dashboard/src/components/sessions/SessionDetail.spec.tsclients/web/apps/dashboard/src/components/sessions/SessionDetail.vueclients/web/apps/dashboard/src/components/sessions/SessionFilters.vueclients/web/apps/dashboard/src/components/sessions/SessionList.spec.tsclients/web/apps/dashboard/src/components/sessions/SessionList.vueclients/web/apps/dashboard/src/composables/useAdmin.spec.tsclients/web/apps/dashboard/src/composables/useAdmin.tsclients/web/apps/dashboard/src/types/admin-sessions.tsopenspec/changes/archive/2026-03-28-session-memory-visibility/design.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/exploration.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/proposal.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/specs/client-surfaces/spec.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/specs/memory-visibility/spec.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/specs/sessions/spec.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/state.yamlopenspec/changes/archive/2026-03-28-session-memory-visibility/tasks.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/verify-report.mdopenspec/config.yamlopenspec/specs/client-surfaces/spec.mdopenspec/specs/memory-visibility/spec.mdopenspec/specs/sessions/spec.md
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: sonar
- GitHub Check: pr-checks
- GitHub Check: Cloudflare Pages
🧰 Additional context used
📓 Path-based instructions (8)
**/*
⚙️ CodeRabbit configuration file
**/*: Security first, performance second.
Validate input boundaries, auth/authz implications, and secret management.
Look for behavioral regressions, missing tests, and contract breaks across modules.
Files:
clients/web/apps/chat/src/App.spec.tsopenspec/changes/archive/2026-03-28-session-memory-visibility/state.yamlclients/agent-runtime/src/memory/mod.rsclients/web/apps/chat/src/composables/useGateway.tsclients/web/apps/chat/src/types/chat.tsclients/web/apps/dashboard/src/components/sessions/SessionList.spec.tsopenspec/changes/archive/2026-03-28-session-memory-visibility/exploration.mdclients/web/apps/dashboard/src/components/sessions/SessionFilters.vueopenspec/config.yamlclients/web/apps/chat/src/components/SessionSidebar.spec.tsclients/web/apps/chat/src/App.vueclients/web/apps/chat/src/composables/useGateway.spec.tsclients/web/apps/dashboard/src/components/memory/MemoryStats.spec.tsclients/web/apps/dashboard/src/App.vueclients/web/apps/chat/src/components/SessionSidebar.vueclients/web/apps/dashboard/src/components/sessions/SessionDetail.spec.tsclients/web/apps/dashboard/src/components/memory/MemoryStats.vueclients/web/apps/chat/src/composables/useChat.spec.tsclients/web/apps/dashboard/src/components/memory/MemoryFilters.vueclients/web/apps/dashboard/src/composables/useAdmin.spec.tsclients/web/apps/dashboard/src/components/memory/MemoryList.vueclients/web/apps/dashboard/src/types/admin-sessions.tsopenspec/changes/archive/2026-03-28-session-memory-visibility/specs/client-surfaces/spec.mdclients/web/apps/dashboard/src/components/sessions/SessionDetail.vueclients/agent-runtime/src/memory/hygiene.rsopenspec/changes/archive/2026-03-28-session-memory-visibility/verify-report.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/design.mdclients/web/apps/chat/src/composables/useChat.tsopenspec/changes/archive/2026-03-28-session-memory-visibility/proposal.mdclients/web/apps/dashboard/src/composables/useAdmin.tsopenspec/changes/archive/2026-03-28-session-memory-visibility/specs/memory-visibility/spec.mdclients/agent-runtime/src/gateway/sessions.rsopenspec/specs/sessions/spec.mdclients/web/apps/dashboard/src/components/sessions/SessionList.vueopenspec/specs/memory-visibility/spec.mdclients/agent-runtime/src/gateway/mod.rsopenspec/specs/client-surfaces/spec.mdclients/web/apps/dashboard/src/components/memory/MemoryList.spec.tsopenspec/changes/archive/2026-03-28-session-memory-visibility/specs/sessions/spec.mdclients/agent-runtime/src/memory/traits.rsclients/agent-runtime/src/memory/sqlite.rsclients/agent-runtime/src/gateway/admin.rsopenspec/changes/archive/2026-03-28-session-memory-visibility/tasks.md
clients/agent-runtime/src/**/*.rs
📄 CodeRabbit inference engine (clients/agent-runtime/AGENTS.md)
clients/agent-runtime/src/**/*.rs: Never log secrets, tokens, raw credentials, or sensitive payloads in any logging statements
Avoid unnecessary allocations, clones, and blocking operations to maintain performance and efficiency
Files:
clients/agent-runtime/src/memory/mod.rsclients/agent-runtime/src/memory/hygiene.rsclients/agent-runtime/src/gateway/sessions.rsclients/agent-runtime/src/gateway/mod.rsclients/agent-runtime/src/memory/traits.rsclients/agent-runtime/src/memory/sqlite.rsclients/agent-runtime/src/gateway/admin.rs
clients/agent-runtime/**/*.rs
📄 CodeRabbit inference engine (clients/agent-runtime/AGENTS.md)
Run
cargo fmt --all -- --check,cargo clippy --all-targets -- -D warnings, andcargo testfor code validation, or document which checks were skipped and why
Files:
clients/agent-runtime/src/memory/mod.rsclients/agent-runtime/src/memory/hygiene.rsclients/agent-runtime/src/gateway/sessions.rsclients/agent-runtime/src/gateway/mod.rsclients/agent-runtime/src/memory/traits.rsclients/agent-runtime/src/memory/sqlite.rsclients/agent-runtime/src/gateway/admin.rs
**/*.rs
⚙️ CodeRabbit configuration file
**/*.rs: Focus on Rust idioms, memory safety, and ownership/borrowing correctness.
Flag unnecessary clones, unchecked panics in production paths, and weak error context.
Prioritize unsafe blocks, FFI boundaries, concurrency races, and secret handling.
Files:
clients/agent-runtime/src/memory/mod.rsclients/agent-runtime/src/memory/hygiene.rsclients/agent-runtime/src/gateway/sessions.rsclients/agent-runtime/src/gateway/mod.rsclients/agent-runtime/src/memory/traits.rsclients/agent-runtime/src/memory/sqlite.rsclients/agent-runtime/src/gateway/admin.rs
**/*.{md,mdx}
⚙️ CodeRabbit configuration file
**/*.{md,mdx}: Verify technical accuracy and that docs stay aligned with code changes.
For user-facing docs, check EN/ES parity or explicitly note pending translation gaps.
Files:
openspec/changes/archive/2026-03-28-session-memory-visibility/exploration.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/specs/client-surfaces/spec.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/verify-report.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/design.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/proposal.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/specs/memory-visibility/spec.mdopenspec/specs/sessions/spec.mdopenspec/specs/memory-visibility/spec.mdopenspec/specs/client-surfaces/spec.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/specs/sessions/spec.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/tasks.md
**/*.vue
⚙️ CodeRabbit configuration file
**/*.vue: Enforce Vue 3 Composition API with <script setup>.
Ensure accessibility (A11y) and proper use of Tailwind CSS classes.
Check for proper prop validation and emitted events documentation.
Files:
clients/web/apps/dashboard/src/components/sessions/SessionFilters.vueclients/web/apps/chat/src/App.vueclients/web/apps/dashboard/src/App.vueclients/web/apps/chat/src/components/SessionSidebar.vueclients/web/apps/dashboard/src/components/memory/MemoryStats.vueclients/web/apps/dashboard/src/components/memory/MemoryFilters.vueclients/web/apps/dashboard/src/components/memory/MemoryList.vueclients/web/apps/dashboard/src/components/sessions/SessionDetail.vueclients/web/apps/dashboard/src/components/sessions/SessionList.vue
clients/agent-runtime/src/{security,gateway,tools}/**/*.rs
📄 CodeRabbit inference engine (clients/agent-runtime/AGENTS.md)
Treat
src/security/,src/gateway/,src/tools/as high-risk surfaces and never broaden filesystem/network execution scope without explicit policy checks
Files:
clients/agent-runtime/src/gateway/sessions.rsclients/agent-runtime/src/gateway/mod.rsclients/agent-runtime/src/gateway/admin.rs
clients/agent-runtime/src/{security,gateway,tools,config}/**/*.rs
📄 CodeRabbit inference engine (clients/agent-runtime/AGENTS.md)
Do not silently weaken security policy or access constraints; keep default behavior secure-by-default with deny-by-default where applicable
Files:
clients/agent-runtime/src/gateway/sessions.rsclients/agent-runtime/src/gateway/mod.rsclients/agent-runtime/src/gateway/admin.rs
🧠 Learnings (7)
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/**/*.rs : Avoid unnecessary allocations, clones, and blocking operations to maintain performance and efficiency
Applied to files:
clients/agent-runtime/src/memory/mod.rsclients/agent-runtime/src/memory/sqlite.rs
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/main.rs : Preserve CLI contract unless change is intentional and documented; prefer explicit errors over silent fallback for unsupported critical paths
Applied to files:
clients/agent-runtime/src/memory/mod.rsopenspec/config.yamlclients/agent-runtime/src/memory/sqlite.rs
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/{security,gateway,tools,config}/**/*.rs : Do not silently weaken security policy or access constraints; keep default behavior secure-by-default with deny-by-default where applicable
Applied to files:
openspec/config.yamlclients/agent-runtime/src/gateway/mod.rsclients/agent-runtime/src/gateway/admin.rs
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/**/*.rs : Run `cargo fmt --all -- --check`, `cargo clippy --all-targets -- -D warnings`, and `cargo test` for code validation, or document which checks were skipped and why
Applied to files:
openspec/config.yamlclients/agent-runtime/src/memory/hygiene.rs
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/{security,gateway,tools}/**/*.rs : Treat `src/security/`, `src/gateway/`, `src/tools/` as high-risk surfaces and never broaden filesystem/network execution scope without explicit policy checks
Applied to files:
openspec/config.yamlclients/agent-runtime/src/gateway/mod.rsclients/agent-runtime/src/gateway/admin.rs
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Inspect existing module and adjacent tests before editing; define scope boundary with one concern per PR and avoid mixed feature+refactor+infra patches
Applied to files:
openspec/config.yaml
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Use conventional commit titles and prefer small PRs (size: XS/S/M) when possible
Applied to files:
openspec/config.yaml
🪛 LanguageTool
openspec/changes/archive/2026-03-28-session-memory-visibility/verify-report.md
[style] ~201-~201: To form a complete sentence, be sure to include a subject.
Context: ... but blocks cargo clippy -D warnings. Should be fixed separately. 3. **Missing i18n ...
(MISSING_IT_THERE)
openspec/changes/archive/2026-03-28-session-memory-visibility/specs/memory-visibility/spec.md
[style] ~24-~24: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...ntries from the local SQLite backend. - MUST support the following query parameters:...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
openspec/specs/sessions/spec.md
[style] ~239-~239: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... return a paginated list of sessions. - MUST support query parameters: - status:...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
[style] ~304-~304: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...ll session record including metadata. - MUST include a summary of memory entries ass...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
openspec/specs/memory-visibility/spec.md
[style] ~25-~25: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...ntries from the local SQLite backend. - MUST support the following query parameters:...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
openspec/changes/archive/2026-03-28-session-memory-visibility/specs/sessions/spec.md
[style] ~238-~238: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... return a paginated list of sessions. - MUST support query parameters: - status:...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
[style] ~303-~303: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...ll session record including metadata. - MUST include a summary of memory entries ass...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
🪛 markdownlint-cli2 (0.22.0)
openspec/changes/archive/2026-03-28-session-memory-visibility/exploration.md
[warning] 48-48: Tables should be surrounded by blank lines
(MD058, blanks-around-tables)
openspec/changes/archive/2026-03-28-session-memory-visibility/design.md
[warning] 103-103: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 135-135: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 152-152: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 170-170: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 186-186: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 197-197: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 229-229: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
openspec/specs/memory-visibility/spec.md
[warning] 126-126: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
openspec/changes/archive/2026-03-28-session-memory-visibility/specs/sessions/spec.md
[warning] 127-127: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
openspec/changes/archive/2026-03-28-session-memory-visibility/tasks.md
[warning] 534-534: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
| match state | ||
| .mem | ||
| .list(category.as_ref(), params.session_id.as_deref()) | ||
| .await | ||
| { | ||
| Ok(entries) => { | ||
| let total = entries.len() as u64; | ||
| let offset_usize = offset as usize; | ||
| let limit_usize = limit as usize; | ||
| let paged: Vec<_> = entries | ||
| .into_iter() | ||
| .skip(offset_usize) | ||
| .take(limit_usize) | ||
| .collect(); | ||
| let response = AdminMemoryListResponse { | ||
| entries: paged, | ||
| total, | ||
| limit, | ||
| offset, | ||
| }; | ||
| ( | ||
| StatusCode::OK, | ||
| Json(serde_json::to_value(response).unwrap_or_default()), | ||
| ) |
There was a problem hiding this comment.
Same pagination accuracy issue in list path.
The non-search list path fetches up to 1000 entries (from Memory::list default limit), then calculates total from the in-memory slice length. For large memory stores, this will undercount.
If accurate pagination is needed, the Memory trait would need a count variant that accepts filters. For now, document the limitation.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@clients/agent-runtime/src/gateway/admin.rs` around lines 2220 - 2243, The
current list path uses state.mem.list(...) to fetch up to the backend's default
batch and then sets total = entries.len() which can undercount large stores; add
a clear comment above the state.mem.list(...) call and/or on
AdminMemoryListResponse explaining that Memory::list currently returns a capped
batch and total is only the count of the fetched batch (not the full store), and
as a minimal code change make total conservative by computing total = (offset as
u64) + (paged.len() as u64) so the returned total never under-represents items
after the offset; reference state.mem.list, entries, paged, and
AdminMemoryListResponse when making this change and document the limitation so
callers know a true-count/count API is required for accurate pagination.
| **Clippy**: FAILED (1 error) | ||
|
|
||
| ``` | ||
| error: binding's name is same as existing binding | ||
| --> used_underscore_binding on line 517 in gateway tests | ||
| = note: `-D clippy::used-underscore-binding` implied by `-D warnings` | ||
| ``` |
There was a problem hiding this comment.
Don’t archive this as PASS WITH WARNINGS while validation is still red.
This report says cargo clippy -D warnings failed and chat Vitest still has 5 failing tests. Recording that state as a passing archived verification leaves a false green trail for the change.
As per coding guidelines "Verify technical accuracy and that docs stay aligned with code changes."
Also applies to: 59-64, 210-214
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@openspec/changes/archive/2026-03-28-session-memory-visibility/verify-report.md`
around lines 34 - 40, The report incorrectly archives the run as "PASS WITH
WARNINGS" despite `cargo clippy -D warnings` failing and chat Vitest showing 5
failing tests; update verify-report.md to mark the overall verification as
FAILED (not pass-with-warnings), include the Clippy error text (`binding's name
is same as existing binding` for used_underscore_binding) and the note that chat
Vitest has 5 failing tests, and make the same corrections for the other affected
sections (around the blocks referenced at lines 59-64 and 210-214) so the
document accurately reflects the failing validation state; additionally, fix the
underlying Clippy complaint in the codebase by renaming the binding in the
gateway test function used_underscore_binding to avoid reusing an existing name
(removing the conflicting leading underscore usage) so the Clippy check can
pass.
7e9d085 to
de8ae56
Compare
Deploying corvus with
|
| Latest commit: |
605011f
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://0909e71a.corvus-42x.pages.dev |
| Branch Preview URL: | https://feature-277-session-memory-v.corvus-42x.pages.dev |
There was a problem hiding this comment.
Actionable comments posted: 28
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
clients/agent-runtime/src/gateway/mod.rs (1)
1696-1703:⚠️ Potential issue | 🟠 MajorRun the idempotency guard before
upsert_session().A replay that reuses an existing
X-Idempotency-Keybut changesX-Session-Idreturns"duplicate"only after the new session has already been created/touched. That pollutes history with zero-message sessions and lets a caller manufacture session IDs without ever processing a turn.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@clients/agent-runtime/src/gateway/mod.rs` around lines 1696 - 1703, The idempotency guard is currently applied after session resolution/upsert, allowing a replay with an existing X-Idempotency-Key but a changed X-Session-Id to create/touch a new session; move the idempotency check to run before calling upsert_session() so duplicates return webhook_duplicate_response(...) without creating or touching sessions. Specifically, call webhook_idempotency_key(&headers) and then state.idempotency_store.record_if_new(idempotency_key) (and return webhook_duplicate_response on false) before invoking the function/method that performs session upsert (upsert_session or the code path that resolves/creates sessions) to ensure duplicate requests never mutate session state.
♻️ Duplicate comments (8)
openspec/changes/archive/2026-03-28-session-memory-visibility/tasks.md (2)
511-513:⚠️ Potential issue | 🟡 MinorAdd the missing rustfmt check to the final Rust validation steps.
The checklist includes
cargo testandcargo clippybut omitscargo fmt --all -- --check(or an explicit skip rationale).As per coding guidelines
clients/agent-runtime/**/*.rs: "Runcargo fmt --all -- --check,cargo clippy --all-targets -- -D warnings, andcargo testfor code validation, or document which checks were skipped and why."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@openspec/changes/archive/2026-03-28-session-memory-visibility/tasks.md` around lines 511 - 513, The final Rust validation steps are missing the rustfmt check; update the task list that currently contains the commands `cargo test --manifest-path clients/agent-runtime/Cargo.toml`, `cargo clippy --manifest-path clients/agent-runtime/Cargo.toml --all-targets -- -D warnings`, and `pnpm --dir clients/web --filter `@corvus/dashboard` test` to include `cargo fmt --all -- --check --manifest-path clients/agent-runtime/Cargo.toml` (or add an explicit skip rationale per the guideline) so the `clients/agent-runtime/**/*.rs` formatting check is enforced alongside clippy and tests.
93-93:⚠️ Potential issue | 🟠 MajorDo not standardize truncated token hashes for session scoping.
Using only a short SHA-256 prefix for
token_hashmaterially reduces collision resistance for an auth-bound isolation key. Specify full SHA-256 (or HMAC-SHA-256 with a server secret) consistently in the task plan.Based on learnings: "Do not silently weaken security policy or access constraints; keep default behavior secure-by-default with deny-by-default where applicable."
Also applies to: 145-146
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@openspec/changes/archive/2026-03-28-session-memory-visibility/tasks.md` at line 93, The task text currently instructs computing a `token_hash` as a truncated SHA-256 prefix (first 16 hex chars); change it to require the full SHA-256 digest (or HMAC-SHA-256 using a server-side secret) and remove any guidance to truncate for session scoping or isolation keys; update the `token_hash` description and any references (e.g., the token hashing guidance in the same doc and the related entries at lines referenced as 145-146) to state "full SHA-256 hex digest" or "HMAC-SHA-256(hex) with server secret" and ensure the plan explicitly forbids using truncated prefixes for authentication/authorization keys.clients/web/apps/chat/src/composables/useChat.spec.ts (1)
519-549:⚠️ Potential issue | 🟠 MajorQueue both
/session/listresponses aroundcreateSession().
createSession()still flips the composable intosession_ready, so the readiness watcher issues its ownfetchSessionList()before the explicitawait chat.fetchSessionList()below. These tests only enqueue one response, and in the 404 case it is even queued aftercreateSession(), so the two requests race for a single mock and the assertions can pass/fail for the wrong reason. Queue one response beforecreateSession()for the watcher-driven fetch and a second one for the explicit fetch, or drop the explicit call and assert only on the watcher refresh.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@clients/web/apps/chat/src/composables/useChat.spec.ts` around lines 519 - 549, The test is racing two fetchSessionList() calls because createSession() triggers a watcher-driven fetch; ensure two /session/list responses are queued for both requests (one mockResolvedValueOnce before calling createSession() for the watcher-triggered fetch and a second mockResolvedValueOnce for the explicit await chat.fetchSessionList()), or remove the explicit await chat.fetchSessionList() and only assert the watcher-driven refresh after createSession(); update the test around useChat, createSession(), and fetchSessionList() to enqueue the responses accordingly.openspec/changes/archive/2026-03-28-session-memory-visibility/verify-report.md (1)
34-43:⚠️ Potential issue | 🟠 MajorMake this archived verification report internally consistent.
This file still records a failed Clippy run, failing chat test counts, and failing CS-* matrix rows, but the verdict is
PASS WITH WARNINGSand the note below says those chat failures were fixed later in the same commit. An archived report cannot mix pre-fix failure data with a post-fix verdict — either keep the original failed run and mark the verdict failed, or regenerate the counts, matrix, and verdict from the final state. As per coding guidelines "Verify technical accuracy and that docs stay aligned with code changes."Also applies to: 59-78, 139-147, 210-216
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@openspec/changes/archive/2026-03-28-session-memory-visibility/verify-report.md` around lines 34 - 43, The archived verification report is inconsistent: it shows a Clippy failure, failing chat test counts, and failed CS-* matrix rows but a final verdict of "PASS WITH WARNINGS" with a note that fixes were applied later; update the report so it represents a single consistent state by either (A) restoring the original failing-run data and set the verdict to FAILED, ensuring the Clippy error and chat test failures remain present, or (B) regenerate the report to reflect the post-fix run (update the Clippy status, chat test counts, CS-* matrix rows and any failing-test details) and change the verdict to PASS/WARNINGS accordingly; apply the same correction to the other affected blocks referenced (the sections corresponding to the duplicate ranges) and ensure the Clippy message about "binding's name is same as existing binding" and the note about it being a pre-existing gateway test issue are either preserved or updated consistently with the chosen state.openspec/changes/archive/2026-03-28-session-memory-visibility/proposal.md (1)
101-103: 🧹 Nitpick | 🔵 TrivialSuccess criteria still says "memory entries" but implementation returns
memory_summary.Line 102 states the endpoint returns "memory entries", but per
admin.rs:2148-2162,GET /web/admin/sessions/:idreturnsmemory_summary: HashMap<String, u64>(category counts), not the actual entries. The memory browser endpoint is used to browse entries.📝 Suggested fix
-- [ ] `GET /web/admin/sessions/:id` returns session detail with message count and memory entries +- [ ] `GET /web/admin/sessions/:id` returns session detail with message count and memory summary (entry counts by category)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@openspec/changes/archive/2026-03-28-session-memory-visibility/proposal.md` around lines 101 - 103, Update the success criteria bullet for GET /web/admin/sessions/:id to accurately reflect the implementation: change "returns session detail with message count and memory entries" to indicate it returns a session detail with message count and a memory_summary (HashMap<String,u64> of category counts) as implemented in admin.rs for the GET /web/admin/sessions/:id handler, and optionally add a separate bullet noting that the memory browser endpoint GET /web/admin/memory is used to browse actual memory entries.openspec/changes/archive/2026-03-28-session-memory-visibility/design.md (1)
393-396: 🧹 Nitpick | 🔵 TrivialDoc still specifies
SessionEntryfor/session/listresponse — should be minimal type.The implementation in
sessions.rscorrectly usesUserSessionView(id, started_at, ended_at, message_count, last_activity), but this doc still says[SessionEntry]which includesstatusandmetadatafields. Update to reflect the actual minimal contract.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@openspec/changes/archive/2026-03-28-session-memory-visibility/design.md` around lines 393 - 396, Update the API docs for GET `/session/list` to reflect the minimal response type actually returned by the implementation: replace the reference to SessionEntry with the compact UserSessionView used in sessions.rs (fields: id, started_at, ended_at, message_count, last_activity) and note that the response is scoped to the caller's sessions; ensure the Response example becomes `{ "sessions": [UserSessionView], "total": u64 }` to match the implemented contract.clients/agent-runtime/src/gateway/mod.rs (1)
1680-1691:⚠️ Potential issue | 🔴 CriticalFail closed when session binding fails.
upsert_session()is the only place this request gets bound to the bearer-derivedtoken_hash. If it errors and the turn still runs, the caller keeps using the suppliedX-Session-Idwithout any ownership record, which breaks/session/listscoping and can write into an effectively unowned session.Based on learnings "Do not silently weaken security policy or access constraints; keep default behavior secure-by-default with deny-by-default where applicable".
Also applies to: 1804-1813
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@clients/agent-runtime/src/gateway/mod.rs` around lines 1680 - 1691, The current best-effort behavior on state.mem.upsert_session(&session_id, token_hash.as_deref()) can leave sessions unbound when it errors; change this to fail-closed: if token_hash.is_some() and upsert_session returns Err, stop processing and return an error response (appropriate HTTP error/Status) instead of continuing; keep the original best-effort (log-and-continue) only when token_hash.is_none(). Update the block around utils::extract_bearer_token, compute_token_hash, token_hash, session_id, and state.mem.upsert_session to enforce this fail-closed behavior.clients/web/apps/dashboard/src/composables/useAdmin.ts (1)
35-37:⚠️ Potential issue | 🟠 MajorThe "sequential usage" assumption is already false.
SessionList.vuecan callfetchSessions()again before the previous request finishes when page/filter changes. With one sharedloadingflag and unconditional state writes, an older response can clearloadingor overwritesessions/total/errorafter a newer request has completed. Track a request id or abort the previous list request before committing results; apply the same pattern to the other fetch helpers too.Also applies to: 59-90
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@clients/web/apps/dashboard/src/composables/useAdmin.ts` around lines 35 - 37, The shared loading ref and unconditional writes in useAdmin (e.g., loading, fetchSessions, sessions/total/error) allow race conditions when SessionList.vue triggers fetchSessions twice; change fetchSessions (and the other fetch helpers referenced around lines 59-90) to either track a per-request ID (compare before committing results) or abort the previous request (use AbortController) so that only the latest response updates loading, sessions, total, and error; ensure the chosen approach is applied consistently to all fetch helpers in this composable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@clients/agent-runtime/src/gateway/admin.rs`:
- Around line 2147-2157: The current construction of memory_summary collapses
both backend errors and SQLite's capped list() into an empty HashMap by using
Err(_) => HashMap::new(); update the handler that builds memory_summary (the
match on state.mem.list(None, Some(&session_id))) to treat errors and capped
results distinctly: on Err(e) return an HTTP 500 error (propagate or convert the
error to a 500 response) instead of returning an empty summary, and/or replace
the call to state.mem.list with a dedicated aggregate/count API (implement and
call a new count_by_session(session_id) on the memory store) so results aren't
affected by sqlite.rs list() row cap; ensure you reference the memory API
methods (state.mem.list and the new count_by_session) and session_id when making
the change.
In `@clients/agent-runtime/src/memory/sqlite.rs`:
- Around line 855-863: The upsert_session conflict branch only updates
last_activity so rows previously marked ended by close_stale_sessions() remain
closed and update_session_activity() will skip them; modify the ON CONFLICT(id)
DO UPDATE clause inside upsert_session to reactivate the session by also setting
ended_at = NULL and status = 'active' (and preserve or adjust message_count per
desired semantics) so a resumed session_id becomes active again; reference the
upsert_session function and the sessions table columns (last_activity, ended_at,
status, message_count) when making this change.
- Around line 1015-1037: Ensure caller token actually owns a session_id before
performing any unscoped memory access: in the methods that accept user-supplied
session IDs (store, recall, list, update_session_activity—and any admin handlers
that currently query by session id), perform an ownership check by querying
whether that session_id is present for the caller's token (reuse
list_sessions_for_token(token_hash, ...) or a lightweight SELECT COUNT(*) WHERE
id = ? AND token_hash = ?). If the check fails, return an appropriate
unauthorized/not-found error and do not call upsert_session() or perform the
memory operation; only proceed when the session_id belongs to the caller's token
(or explicitly allow admin paths that perform a separate, intentional
authorization check).
In `@clients/agent-runtime/src/memory/traits.rs`:
- Around line 171-220: The trait default implementations for upsert_session,
end_session, update_session_activity, list_sessions, get_session,
list_sessions_for_token, and memory_stats silently return success/empty results
causing LucidMemory, MarkdownMemory, and NoneMemory to appear to support
sessions; change the trait defaults in traits.rs to return Err(anyhow!("sessions
unsupported")) so unsupported backends fail fast, and/or implement these methods
on LucidMemory/MarkdownMemory/NoneMemory if they should support sessions;
additionally, update gateway callers in admin.rs and sessions.rs to either check
the backend type (reject non-SQLite backends) or propagate and translate the
error into a 4xx response, and add tests that call these session/stats APIs
against non-SQLite backends to assert they return the unsupported error.
In `@clients/web/apps/chat/src/App.vue`:
- Line 38: The SessionSidebar is being rendered uncollapsed by default because
sidebarCollapsed is initialized to false; change the logic that sets
sidebarCollapsed (the ref created as sidebarCollapsed) so it defaults to true on
narrow viewports (e.g., using window.matchMedia or a lightweight useMediaQuery
check on mount) or toggle the component into a drawer mode for small screens;
update the initialization and any mount effect that currently sets
sidebarCollapsed (and similar usage around the SessionSidebar render blocks
referenced near the other occurrences) so phones and small tablets start with
the sidebar collapsed/hidden and only expand when the user taps the collapse
toggle.
- Around line 261-273: The parsed session restore must validate each entry's
shape before accepting it: instead of only checking Array.isArray(parsed) and
length, verify every item in parsed conforms to Message (e.g., has required
fields like id (finite number), role, content) and that parsed.map(m => m.id)
yields all finite numbers; if any entry fails validation, call
resetMessagesForSession() and do not set messages.value or messageIdCounter;
otherwise set messages.value = parsed and set messageIdCounter =
Math.max(...parsed.map(m => m.id)) + 1. Use the existing variables parsed,
messages.value, messageIdCounter, and resetMessagesForSession in the check.
In `@clients/web/apps/chat/src/composables/useChat.spec.ts`:
- Around line 121-123: The test is indexing fetchMock.mock.calls[1] which is
flaky; instead locate the specific fetch call for the message request by
matching its URL/body before asserting headers. Replace the fixed index usage
(the line using const [, init] = fetchMock.mock.calls[1]) with code that
searches fetchMock.mock.calls for the entry whose first argument (request URL)
or second argument (init/body) matches the message endpoint or payload (e.g.,
includes '/message' or the message body), then assert headers against that
matched call's init; use fetchMock.mock.calls.find(...) and guard for not-found
to keep the test stable.
In `@clients/web/apps/chat/src/composables/useChat.ts`:
- Around line 541-543: getSessionList() returns sessions missing the ended_at
field but sessionList is typed as SessionListItem[] (requiring ended_at: string
| null), so map result.sessions before assigning to sessionList.value and ensure
each item includes ended_at (defaulting undefined to null); update the
assignment in the block that calls gateway.getSessionList() to transform each
session (preserving other fields) and set ended_at = session.ended_at ?? null so
the array conforms to SessionListItem.
In `@clients/web/apps/chat/src/composables/useGateway.spec.ts`:
- Around line 485-487: The test assertions enforce the wrong pagination query
params (page/per_page) which conflicts with the session API spec that uses
limit/offset; update the expectations in useGateway.spec.ts to assert that the
generated url contains "limit=20" and "offset=0" (or the appropriate values used
by the test) instead of "page=1" and "per_page=20", and keep the header
assertion for Authorization (init?.headers) intact; also update the equivalent
assertions mentioned at lines 522-523 to the same limit/offset checks so the
test matches the documented GET /session/list contract.
- Around line 466-468: The mocked session objects in the test fixture (entries
with id "s1" and "s2") are missing the ended_at field, so update those session
payloads to include ended_at (use a timestamp string or null as appropriate for
active vs ended sessions) to match the API response shape and allow the
active-vs-ended decoding assertions in useGateway.spec.ts to run correctly;
locate the array literal containing { id: "s1", ... } and { id: "s2", ... } and
add an ended_at property to each object.
In `@clients/web/apps/chat/src/composables/useGateway.ts`:
- Around line 507-509: The catch block that returns { sessions: [], total: 0 }
in useGateway.ts is swallowing authentication errors (401/403) thrown earlier;
update the catch to detect auth errors (e.g., error.status or
error.response?.status) and re-throw them so callers/UI can handle invalid
credentials, but keep returning the empty fallback only for non-auth errors;
locate the catch that returns the sessions/total object in the function that
loads sessions (the error originates from the earlier auth check) and implement
the conditional re-throw for status 401 and 403.
In `@clients/web/apps/dashboard/src/App.vue`:
- Around line 129-150: The nav switcher lacks accessibility semantics—update the
nav element and buttons in App.vue to expose the selected view: add
role="tablist" to the <nav> and on each button (the ones toggling currentPage)
add role="tab" and bind aria-selected (e.g., aria-selected="currentPage ===
'config'") or aria-current/aria-pressed tied to currentPage, ensuring the class
toggle still uses currentPage; update attributes on the buttons that call
`@click`="currentPage = '...'" so screen readers can detect the active tab.
In `@clients/web/apps/dashboard/src/components/memory/MemoryList.vue`:
- Around line 139-155: The dialog uses confirmingDelete and methods
confirmDelete/cancelDelete but lacks a focus trap; when confirmingDelete becomes
truthy, add a focus-trap behavior in MemoryList.vue to move focus into the
dialog (set initial focus to the confirm-yes button), trap Tab/Shift+Tab inside
the .confirm-dialog, handle Escape to call cancelDelete, and restore previous
focus when cancelling or after confirmDelete; ensure this logic runs only while
confirmingDelete is true and cleans up event listeners on teardown.
- Around line 49-57: In confirmDelete(), the page clamping uses stale
totalPages() before the list is reloaded; change the flow in confirmDelete to
call await load() first (after successful admin.deleteMemoryEntry) and then
recompute and clamp page.value against totalPages(), or alternatively adjust an
expected totalPages decrement before load and then clamp after load; update
references inside the confirmDelete function (confirmingDelete,
admin.deleteMemoryEntry, load, page, totalPages) so page.value is set based on
the post-delete total rather than the pre-delete value.
In `@clients/web/apps/dashboard/src/components/sessions/SessionDetail.spec.ts`:
- Around line 1-2: Tests currently call vi.dynamicImportSettled(), which doesn't
wait for fetch-based async work; import and use flushPromises() instead to
reliably wait for component-mounted fetches. Update the imports at the top of
SessionDetail.spec.ts to add "import flushPromises from 'flush-promises';" (and
keep existing mount/beforeEach/describe/expect/it/vi imports), then replace
every call to vi.dynamicImportSettled() with "await flushPromises()" inside the
async test handlers (e.g., in the tests that mount SessionDetail and assert
fetched data). Ensure the test functions are async where you add await
flushPromises(), and remove reliance on vi.dynamicImportSettled() in all
occurrences in this file.
In `@clients/web/apps/dashboard/src/components/sessions/SessionDetail.vue`:
- Around line 24-25: The component currently calls load() twice on mount because
both onMounted() and watch(() => props.sessionId, load) invoke it; remove the
onMounted call and change the watcher to use an immediate invocation by updating
the watcher on props.sessionId (the watch(() => props.sessionId, load) call) to
include { immediate: true } so load() runs once on mount and whenever sessionId
changes.
In `@clients/web/apps/dashboard/src/components/sessions/SessionFilters.vue`:
- Around line 14-21: The component currently only emits status and sort when the
user changes them, so initial parent/child state can stay out of sync; call the
existing onStatusChange and onSortChange during component mount (use onMounted)
so the component emits its initial values for status (derived from status.value)
and sort (from sort.value) to the parent via emit("update:status", ...) and
emit("update:sort", ...); add an onMounted(() => { onStatusChange();
onSortChange(); }) block to ensure initial sync while reusing the existing
onStatusChange and onSortChange functions.
In `@clients/web/apps/dashboard/src/components/sessions/SessionList.vue`:
- Around line 78-87: The table row currently has role="button" and keyboard
handlers (tr in v-for over admin.sessions.value with class "session-row" calling
emit('select', session)), which is inaccessible; remove interactive semantics
from the <tr> and instead place a real interactive element (button or link)
inside a <td> to handle activation. Update the element that currently triggers
selection to be a <button> (or <a>) inside the row that calls emit('select',
session) on click and handles both Enter and Space (or relies on native button
behavior) and style it to fill the row (use Tailwind utility classes to make it
display:block / w-full / text-left) so keyboard and screen-reader users get
correct semantics; also remove role="button" and key handlers from the tr and
keep any necessary data-testid on the button or a containing cell.
In
`@openspec/changes/archive/2026-03-28-session-memory-visibility/specs/sessions/spec.md`:
- Around line 20-31: The session schema currently lacks any persisted link
between sessions and user/token, making SESS-8 (user-scoped /session/list)
unimplementable; add a deterministic token/user identifier column (e.g., user_id
TEXT or token_id TEXT) to the sessions table (and/or a separate sessions_tokens
mapping table if you prefer many-to-many), mark it NOT NULL (or NULL if
anonymous sessions allowed), store token/user identifiers when creating Session
records in the code paths that call create/update session, and add an index on
this new column to support efficient user-scoped listing/filtering used by
/session/list and related handlers.
- Around line 374-383: The SESS-9 spec is missing several session/stat methods
added in the runtime; update the "Memory" trait section to include the full API
surface and note default no-op implementations: add method signatures and
defaults for update_session_activity(session_id: &str, activity:
SessionActivity) -> Result<()>, list_sessions_for_token(token: &str, filter:
SessionFilter) -> Result<Vec<Session>> (token-scoped listing), and
get_session_stats(session_id: &str) -> Result<SessionStats> (session
statistics), in addition to the existing start_session, end_session,
list_sessions, and get_session, and state that all these MUST default to Ok with
empty/default values so non-SQLite backends remain compatible; reference the
trait name Memory and the method names above when updating the markdown.
In `@openspec/changes/archive/2026-03-28-session-memory-visibility/tasks.md`:
- Around line 283-284: Update the task docs entry for the session list shape to
include the new ended_at field: modify the SessionListItem interface description
to list id, started_at, ended_at, message_count, last_activity and ensure
SessionListResponse remains sessions and total so the documented shape matches
the implemented chat contract (update the lines referencing SessionListItem in
tasks.md accordingly).
In `@openspec/config.yaml`:
- Around line 26-28: Restore explicit test-first enforcement in the apply
section by reintroducing tdd: true and a required test_command entry so PRs with
metadata risk:high or risk:security (especially touching gateway endpoints,
session persistence, or memory controls) must include a test-first workflow;
additionally require adding threat/risk notes and a rollback strategy for
security/runtime/gateway changes and mandate accompanying tests that cover
boundary checks and failure modes for the affected areas (gateway endpoints,
session persistence, memory controls).
- Around line 29-31: Update the verify checklist to explicitly expand "per-app
checks" to name the targets (make chat-test and make dashboard-test) and add
discrete Rust validation steps: include "cargo fmt --all -- --check", "cargo
clippy --all-targets -- -D warnings", and "cargo test" as separate verify
entries; retain the existing make rust-test and make web-test-all items but list
these Rust checks so they are explicitly required for compliance and easy to
follow.
In `@openspec/specs/client-surfaces/spec.md`:
- Around line 305-481: MD022: Add a single blank line immediately before each
"#### Scenario:" heading to satisfy the markdown lint rule; update the
occurrences such as "#### Scenario: Dashboard displays session list", "####
Scenario: Filter sessions by active status", "#### Scenario: Admin views session
detail", "#### Scenario: Admin deletes a memory entry", "#### Scenario: Memory
stats panel displays correctly", "#### Scenario: Chat sidebar lists past
sessions", "#### Scenario: User switches to a past session", "#### Scenario:
Session list loads from server on mount", and "#### Scenario: Chat does not
expose memory data" by inserting one blank line above each heading in the
spec.md content so every scenario block is separated from the previous
paragraph/heading.
In `@openspec/specs/memory-visibility/spec.md`:
- Around line 25-33: The spec and client disagree on MemoryCategory
casing—update the canonical enum casing used across the spec and typed contract
so it matches the shipped client (choose either PascalCase e.g.,
"Core","Conversation" or lowercase e.g., "core","conversation") and make the
change consistently: adjust the `MemoryCategory` enum declaration, all
`category` query parameter examples, request/response examples, and any typed
contract references so they use the chosen casing (ensure returned entry fields
still include `id`, `key`, `content`, `category`, `timestamp`, `session_id` but
with the normalized enum values).
In `@openspec/specs/sessions/spec.md`:
- Around line 237-256: The spec is missing the `status` field in admin session
responses which the dashboard client and tests expect; update the admin sessions
list and detail response schemas (the representations referenced by
AdminSessionView and AdminSessionDetail) to include a `status` property with
allowed values `active`, `ended` (and `all` only for query), add `status` to the
session object examples in the GET /web/admin/sessions scenario and the detail
scenario, and ensure the response contract for both list and single-session
endpoints documents `status` (and its enum) so fixtures in useAdmin.spec.ts
match the spec.
- Around line 21-32: The spec is missing how token-to-session ownership is
persisted for the runtime call upsert_session(..., token_hash) and for
/session/list filtering: add a persistent ownership mapping (either by adding a
sessions table column named token_hash (TEXT) — e.g., token_hash NOT NULL with
an index — or by defining a separate session_ownership table with columns
session_id TEXT and token_hash TEXT and an index on token_hash); update the
sessions table definition to include the chosen ownership field/table and state
that /session/list must filter sessions by token_hash to return token-scoped
history, and reference upsert_session and /session/list as the API points that
rely on this mapping.
- Around line 377-384: The spec's Memory trait is out of date: it documents
start_session/end_session/list_sessions/get_session but the runtime and gateway
call upsert_session() and update_session_activity() and rely on token-scoped
listing helpers; update the spec to use the actual method names and signatures
used at runtime (rename start_session → upsert_session(session_id: &str,
session: Session) -> Result<Session> or appropriate params, add
update_session_activity(session_id: &str, last_active: Timestamp) -> Result<()>,
and add token-scoped list methods used by the gateway), and ensure each method
has default implementations returning Ok with empty/default values so non-SQLite
backends remain compatible; reference upsert_session and update_session_activity
in the updated text and mirror the precise types/signatures used by the
codebase.
---
Outside diff comments:
In `@clients/agent-runtime/src/gateway/mod.rs`:
- Around line 1696-1703: The idempotency guard is currently applied after
session resolution/upsert, allowing a replay with an existing X-Idempotency-Key
but a changed X-Session-Id to create/touch a new session; move the idempotency
check to run before calling upsert_session() so duplicates return
webhook_duplicate_response(...) without creating or touching sessions.
Specifically, call webhook_idempotency_key(&headers) and then
state.idempotency_store.record_if_new(idempotency_key) (and return
webhook_duplicate_response on false) before invoking the function/method that
performs session upsert (upsert_session or the code path that resolves/creates
sessions) to ensure duplicate requests never mutate session state.
---
Duplicate comments:
In `@clients/agent-runtime/src/gateway/mod.rs`:
- Around line 1680-1691: The current best-effort behavior on
state.mem.upsert_session(&session_id, token_hash.as_deref()) can leave sessions
unbound when it errors; change this to fail-closed: if token_hash.is_some() and
upsert_session returns Err, stop processing and return an error response
(appropriate HTTP error/Status) instead of continuing; keep the original
best-effort (log-and-continue) only when token_hash.is_none(). Update the block
around utils::extract_bearer_token, compute_token_hash, token_hash, session_id,
and state.mem.upsert_session to enforce this fail-closed behavior.
In `@clients/web/apps/chat/src/composables/useChat.spec.ts`:
- Around line 519-549: The test is racing two fetchSessionList() calls because
createSession() triggers a watcher-driven fetch; ensure two /session/list
responses are queued for both requests (one mockResolvedValueOnce before calling
createSession() for the watcher-triggered fetch and a second
mockResolvedValueOnce for the explicit await chat.fetchSessionList()), or remove
the explicit await chat.fetchSessionList() and only assert the watcher-driven
refresh after createSession(); update the test around useChat, createSession(),
and fetchSessionList() to enqueue the responses accordingly.
In `@clients/web/apps/dashboard/src/composables/useAdmin.ts`:
- Around line 35-37: The shared loading ref and unconditional writes in useAdmin
(e.g., loading, fetchSessions, sessions/total/error) allow race conditions when
SessionList.vue triggers fetchSessions twice; change fetchSessions (and the
other fetch helpers referenced around lines 59-90) to either track a per-request
ID (compare before committing results) or abort the previous request (use
AbortController) so that only the latest response updates loading, sessions,
total, and error; ensure the chosen approach is applied consistently to all
fetch helpers in this composable.
In `@openspec/changes/archive/2026-03-28-session-memory-visibility/design.md`:
- Around line 393-396: Update the API docs for GET `/session/list` to reflect
the minimal response type actually returned by the implementation: replace the
reference to SessionEntry with the compact UserSessionView used in sessions.rs
(fields: id, started_at, ended_at, message_count, last_activity) and note that
the response is scoped to the caller's sessions; ensure the Response example
becomes `{ "sessions": [UserSessionView], "total": u64 }` to match the
implemented contract.
In `@openspec/changes/archive/2026-03-28-session-memory-visibility/proposal.md`:
- Around line 101-103: Update the success criteria bullet for GET
/web/admin/sessions/:id to accurately reflect the implementation: change
"returns session detail with message count and memory entries" to indicate it
returns a session detail with message count and a memory_summary
(HashMap<String,u64> of category counts) as implemented in admin.rs for the GET
/web/admin/sessions/:id handler, and optionally add a separate bullet noting
that the memory browser endpoint GET /web/admin/memory is used to browse actual
memory entries.
In `@openspec/changes/archive/2026-03-28-session-memory-visibility/tasks.md`:
- Around line 511-513: The final Rust validation steps are missing the rustfmt
check; update the task list that currently contains the commands `cargo test
--manifest-path clients/agent-runtime/Cargo.toml`, `cargo clippy --manifest-path
clients/agent-runtime/Cargo.toml --all-targets -- -D warnings`, and `pnpm --dir
clients/web --filter `@corvus/dashboard` test` to include `cargo fmt --all --
--check --manifest-path clients/agent-runtime/Cargo.toml` (or add an explicit
skip rationale per the guideline) so the `clients/agent-runtime/**/*.rs`
formatting check is enforced alongside clippy and tests.
- Line 93: The task text currently instructs computing a `token_hash` as a
truncated SHA-256 prefix (first 16 hex chars); change it to require the full
SHA-256 digest (or HMAC-SHA-256 using a server-side secret) and remove any
guidance to truncate for session scoping or isolation keys; update the
`token_hash` description and any references (e.g., the token hashing guidance in
the same doc and the related entries at lines referenced as 145-146) to state
"full SHA-256 hex digest" or "HMAC-SHA-256(hex) with server secret" and ensure
the plan explicitly forbids using truncated prefixes for
authentication/authorization keys.
In
`@openspec/changes/archive/2026-03-28-session-memory-visibility/verify-report.md`:
- Around line 34-43: The archived verification report is inconsistent: it shows
a Clippy failure, failing chat test counts, and failed CS-* matrix rows but a
final verdict of "PASS WITH WARNINGS" with a note that fixes were applied later;
update the report so it represents a single consistent state by either (A)
restoring the original failing-run data and set the verdict to FAILED, ensuring
the Clippy error and chat test failures remain present, or (B) regenerate the
report to reflect the post-fix run (update the Clippy status, chat test counts,
CS-* matrix rows and any failing-test details) and change the verdict to
PASS/WARNINGS accordingly; apply the same correction to the other affected
blocks referenced (the sections corresponding to the duplicate ranges) and
ensure the Clippy message about "binding's name is same as existing binding" and
the note about it being a pre-existing gateway test issue are either preserved
or updated consistently with the chosen state.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: dc10d084-89e8-4a9d-943d-fce882c37c1f
📒 Files selected for processing (43)
clients/agent-runtime/src/gateway/admin.rsclients/agent-runtime/src/gateway/mod.rsclients/agent-runtime/src/gateway/sessions.rsclients/agent-runtime/src/memory/hygiene.rsclients/agent-runtime/src/memory/mod.rsclients/agent-runtime/src/memory/sqlite.rsclients/agent-runtime/src/memory/traits.rsclients/web/apps/chat/src/App.spec.tsclients/web/apps/chat/src/App.vueclients/web/apps/chat/src/components/SessionSidebar.spec.tsclients/web/apps/chat/src/components/SessionSidebar.vueclients/web/apps/chat/src/composables/useChat.spec.tsclients/web/apps/chat/src/composables/useChat.tsclients/web/apps/chat/src/composables/useGateway.spec.tsclients/web/apps/chat/src/composables/useGateway.tsclients/web/apps/chat/src/types/chat.tsclients/web/apps/dashboard/src/App.vueclients/web/apps/dashboard/src/components/memory/MemoryFilters.vueclients/web/apps/dashboard/src/components/memory/MemoryList.spec.tsclients/web/apps/dashboard/src/components/memory/MemoryList.vueclients/web/apps/dashboard/src/components/memory/MemoryStats.spec.tsclients/web/apps/dashboard/src/components/memory/MemoryStats.vueclients/web/apps/dashboard/src/components/sessions/SessionDetail.spec.tsclients/web/apps/dashboard/src/components/sessions/SessionDetail.vueclients/web/apps/dashboard/src/components/sessions/SessionFilters.vueclients/web/apps/dashboard/src/components/sessions/SessionList.spec.tsclients/web/apps/dashboard/src/components/sessions/SessionList.vueclients/web/apps/dashboard/src/composables/useAdmin.spec.tsclients/web/apps/dashboard/src/composables/useAdmin.tsclients/web/apps/dashboard/src/types/admin-sessions.tsopenspec/changes/archive/2026-03-28-session-memory-visibility/design.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/exploration.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/proposal.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/specs/client-surfaces/spec.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/specs/memory-visibility/spec.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/specs/sessions/spec.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/state.yamlopenspec/changes/archive/2026-03-28-session-memory-visibility/tasks.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/verify-report.mdopenspec/config.yamlopenspec/specs/client-surfaces/spec.mdopenspec/specs/memory-visibility/spec.mdopenspec/specs/sessions/spec.md
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: pr-checks
- GitHub Check: sonar
- GitHub Check: Cloudflare Pages
🧰 Additional context used
📓 Path-based instructions (8)
**/*
⚙️ CodeRabbit configuration file
**/*: Security first, performance second.
Validate input boundaries, auth/authz implications, and secret management.
Look for behavioral regressions, missing tests, and contract breaks across modules.
Files:
openspec/changes/archive/2026-03-28-session-memory-visibility/state.yamlclients/agent-runtime/src/memory/mod.rsclients/web/apps/chat/src/types/chat.tsclients/web/apps/chat/src/composables/useGateway.spec.tsclients/web/apps/chat/src/App.spec.tsclients/web/apps/dashboard/src/components/sessions/SessionFilters.vueopenspec/config.yamlclients/web/apps/chat/src/components/SessionSidebar.spec.tsclients/web/apps/dashboard/src/components/memory/MemoryStats.spec.tsclients/web/apps/chat/src/composables/useGateway.tsclients/web/apps/dashboard/src/components/sessions/SessionDetail.spec.tsclients/web/apps/dashboard/src/components/memory/MemoryList.spec.tsclients/web/apps/dashboard/src/components/memory/MemoryStats.vueopenspec/changes/archive/2026-03-28-session-memory-visibility/proposal.mdclients/web/apps/chat/src/composables/useChat.tsopenspec/changes/archive/2026-03-28-session-memory-visibility/design.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/exploration.mdclients/agent-runtime/src/memory/hygiene.rsclients/web/apps/dashboard/src/components/sessions/SessionDetail.vueclients/web/apps/dashboard/src/components/memory/MemoryFilters.vueclients/web/apps/dashboard/src/types/admin-sessions.tsopenspec/changes/archive/2026-03-28-session-memory-visibility/tasks.mdclients/web/apps/dashboard/src/components/memory/MemoryList.vueopenspec/changes/archive/2026-03-28-session-memory-visibility/specs/memory-visibility/spec.mdclients/web/apps/dashboard/src/App.vueclients/web/apps/chat/src/composables/useChat.spec.tsclients/agent-runtime/src/gateway/sessions.rsopenspec/specs/client-surfaces/spec.mdopenspec/specs/memory-visibility/spec.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/specs/client-surfaces/spec.mdclients/web/apps/dashboard/src/composables/useAdmin.tsopenspec/specs/sessions/spec.mdclients/web/apps/dashboard/src/composables/useAdmin.spec.tsopenspec/changes/archive/2026-03-28-session-memory-visibility/verify-report.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/specs/sessions/spec.mdclients/agent-runtime/src/gateway/mod.rsclients/web/apps/chat/src/App.vueclients/web/apps/chat/src/components/SessionSidebar.vueclients/agent-runtime/src/gateway/admin.rsclients/agent-runtime/src/memory/traits.rsclients/web/apps/dashboard/src/components/sessions/SessionList.spec.tsclients/agent-runtime/src/memory/sqlite.rsclients/web/apps/dashboard/src/components/sessions/SessionList.vue
clients/agent-runtime/src/**/*.rs
📄 CodeRabbit inference engine (clients/agent-runtime/AGENTS.md)
clients/agent-runtime/src/**/*.rs: Never log secrets, tokens, raw credentials, or sensitive payloads in any logging statements
Avoid unnecessary allocations, clones, and blocking operations to maintain performance and efficiency
Files:
clients/agent-runtime/src/memory/mod.rsclients/agent-runtime/src/memory/hygiene.rsclients/agent-runtime/src/gateway/sessions.rsclients/agent-runtime/src/gateway/mod.rsclients/agent-runtime/src/gateway/admin.rsclients/agent-runtime/src/memory/traits.rsclients/agent-runtime/src/memory/sqlite.rs
clients/agent-runtime/**/*.rs
📄 CodeRabbit inference engine (clients/agent-runtime/AGENTS.md)
Run
cargo fmt --all -- --check,cargo clippy --all-targets -- -D warnings, andcargo testfor code validation, or document which checks were skipped and why
Files:
clients/agent-runtime/src/memory/mod.rsclients/agent-runtime/src/memory/hygiene.rsclients/agent-runtime/src/gateway/sessions.rsclients/agent-runtime/src/gateway/mod.rsclients/agent-runtime/src/gateway/admin.rsclients/agent-runtime/src/memory/traits.rsclients/agent-runtime/src/memory/sqlite.rs
**/*.rs
⚙️ CodeRabbit configuration file
**/*.rs: Focus on Rust idioms, memory safety, and ownership/borrowing correctness.
Flag unnecessary clones, unchecked panics in production paths, and weak error context.
Prioritize unsafe blocks, FFI boundaries, concurrency races, and secret handling.
Files:
clients/agent-runtime/src/memory/mod.rsclients/agent-runtime/src/memory/hygiene.rsclients/agent-runtime/src/gateway/sessions.rsclients/agent-runtime/src/gateway/mod.rsclients/agent-runtime/src/gateway/admin.rsclients/agent-runtime/src/memory/traits.rsclients/agent-runtime/src/memory/sqlite.rs
**/*.vue
⚙️ CodeRabbit configuration file
**/*.vue: Enforce Vue 3 Composition API with <script setup>.
Ensure accessibility (A11y) and proper use of Tailwind CSS classes.
Check for proper prop validation and emitted events documentation.
Files:
clients/web/apps/dashboard/src/components/sessions/SessionFilters.vueclients/web/apps/dashboard/src/components/memory/MemoryStats.vueclients/web/apps/dashboard/src/components/sessions/SessionDetail.vueclients/web/apps/dashboard/src/components/memory/MemoryFilters.vueclients/web/apps/dashboard/src/components/memory/MemoryList.vueclients/web/apps/dashboard/src/App.vueclients/web/apps/chat/src/App.vueclients/web/apps/chat/src/components/SessionSidebar.vueclients/web/apps/dashboard/src/components/sessions/SessionList.vue
**/*.{md,mdx}
⚙️ CodeRabbit configuration file
**/*.{md,mdx}: Verify technical accuracy and that docs stay aligned with code changes.
For user-facing docs, check EN/ES parity or explicitly note pending translation gaps.
Files:
openspec/changes/archive/2026-03-28-session-memory-visibility/proposal.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/design.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/exploration.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/tasks.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/specs/memory-visibility/spec.mdopenspec/specs/client-surfaces/spec.mdopenspec/specs/memory-visibility/spec.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/specs/client-surfaces/spec.mdopenspec/specs/sessions/spec.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/verify-report.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/specs/sessions/spec.md
clients/agent-runtime/src/{security,gateway,tools}/**/*.rs
📄 CodeRabbit inference engine (clients/agent-runtime/AGENTS.md)
Treat
src/security/,src/gateway/,src/tools/as high-risk surfaces and never broaden filesystem/network execution scope without explicit policy checks
Files:
clients/agent-runtime/src/gateway/sessions.rsclients/agent-runtime/src/gateway/mod.rsclients/agent-runtime/src/gateway/admin.rs
clients/agent-runtime/src/{security,gateway,tools,config}/**/*.rs
📄 CodeRabbit inference engine (clients/agent-runtime/AGENTS.md)
Do not silently weaken security policy or access constraints; keep default behavior secure-by-default with deny-by-default where applicable
Files:
clients/agent-runtime/src/gateway/sessions.rsclients/agent-runtime/src/gateway/mod.rsclients/agent-runtime/src/gateway/admin.rs
🧠 Learnings (12)
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/{security,gateway,tools,config}/**/*.rs : Do not silently weaken security policy or access constraints; keep default behavior secure-by-default with deny-by-default where applicable
Applied to files:
openspec/config.yamlopenspec/changes/archive/2026-03-28-session-memory-visibility/tasks.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/specs/memory-visibility/spec.mdclients/agent-runtime/src/gateway/mod.rsclients/agent-runtime/src/gateway/admin.rs
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/**/*.rs : Run `cargo fmt --all -- --check`, `cargo clippy --all-targets -- -D warnings`, and `cargo test` for code validation, or document which checks were skipped and why
Applied to files:
openspec/config.yamlclients/agent-runtime/src/memory/hygiene.rsopenspec/changes/archive/2026-03-28-session-memory-visibility/tasks.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/verify-report.mdclients/agent-runtime/src/memory/traits.rs
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Inspect existing module and adjacent tests before editing; define scope boundary with one concern per PR and avoid mixed feature+refactor+infra patches
Applied to files:
openspec/config.yaml
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Include threat/risk notes and rollback strategy for security, runtime, and gateway changes; add or update tests for boundary checks and failure modes
Applied to files:
openspec/config.yaml
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: If uncertain, leave a concrete TODO with verification context instead of hidden guesses
Applied to files:
openspec/config.yaml
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/{security,gateway,tools}/**/*.rs : Treat `src/security/`, `src/gateway/`, `src/tools/` as high-risk surfaces and never broaden filesystem/network execution scope without explicit policy checks
Applied to files:
openspec/config.yamlopenspec/changes/archive/2026-03-28-session-memory-visibility/tasks.mdclients/agent-runtime/src/gateway/mod.rsclients/agent-runtime/src/gateway/admin.rs
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/main.rs : Preserve CLI contract unless change is intentional and documented; prefer explicit errors over silent fallback for unsupported critical paths
Applied to files:
openspec/changes/archive/2026-03-28-session-memory-visibility/tasks.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/verify-report.mdclients/agent-runtime/src/gateway/mod.rsclients/agent-runtime/src/gateway/admin.rsclients/agent-runtime/src/memory/traits.rsclients/agent-runtime/src/memory/sqlite.rs
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/tools/**/*.rs : Implement `Tool` trait in `src/tools/` with strict parameter schema, validate and sanitize all inputs, and return structured `ToolResult` without panics in runtime path
Applied to files:
openspec/changes/archive/2026-03-28-session-memory-visibility/tasks.md
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/**/Cargo.toml : Do not add heavy dependencies for minor convenience; justify new crate additions
Applied to files:
openspec/changes/archive/2026-03-28-session-memory-visibility/tasks.md
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/**/Cargo.toml : Preserve release-size profile assumptions in `Cargo.toml` and avoid adding heavy dependencies unless clearly justified
Applied to files:
openspec/changes/archive/2026-03-28-session-memory-visibility/tasks.md
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/channels/**/*.rs : Implement `Channel` trait in `src/channels/` with consistent `send`, `listen`, and `health_check` semantics and cover auth/allowlist/health behavior with tests
Applied to files:
clients/agent-runtime/src/gateway/admin.rs
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/**/*.rs : Avoid unnecessary allocations, clones, and blocking operations to maintain performance and efficiency
Applied to files:
clients/agent-runtime/src/memory/traits.rs
🪛 LanguageTool
openspec/changes/archive/2026-03-28-session-memory-visibility/specs/memory-visibility/spec.md
[style] ~24-~24: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...ntries from the local SQLite backend. - MUST support the following query parameters:...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
openspec/specs/memory-visibility/spec.md
[style] ~25-~25: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...ntries from the local SQLite backend. - MUST support the following query parameters:...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
openspec/specs/sessions/spec.md
[style] ~239-~239: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... return a paginated list of sessions. - MUST support query parameters: - status:...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
[style] ~304-~304: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...ll session record including metadata. - MUST include a summary of memory entries ass...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
openspec/changes/archive/2026-03-28-session-memory-visibility/verify-report.md
[style] ~201-~201: To form a complete sentence, be sure to include a subject.
Context: ... but blocks cargo clippy -D warnings. Should be fixed separately. 3. **Missing i18n ...
(MISSING_IT_THERE)
openspec/changes/archive/2026-03-28-session-memory-visibility/specs/sessions/spec.md
[style] ~238-~238: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... return a paginated list of sessions. - MUST support query parameters: - status:...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
[style] ~303-~303: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...ll session record including metadata. - MUST include a summary of memory entries ass...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
🪛 markdownlint-cli2 (0.22.0)
openspec/specs/client-surfaces/spec.md
[warning] 305-305: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 313-313: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 329-329: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 349-349: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 365-365: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 388-388: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 396-396: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 412-412: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 427-427: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 443-443: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 457-457: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 477-477: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 481-481: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
openspec/changes/archive/2026-03-28-session-memory-visibility/verify-report.md
[warning] 28-28: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 36-36: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 48-48: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 61-61: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 71-71: Trailing spaces
Expected: 0 or 2; Actual: 3
(MD009, no-trailing-spaces)
| async fn list_sessions_for_token( | ||
| &self, | ||
| token_hash: &str, | ||
| limit: u32, | ||
| offset: u32, | ||
| ) -> anyhow::Result<(Vec<SessionEntry>, u64)> { | ||
| let conn = self.conn.clone(); | ||
| let token_hash = token_hash.to_string(); | ||
|
|
||
| tokio::task::spawn_blocking(move || -> anyhow::Result<(Vec<SessionEntry>, u64)> { | ||
| let conn = conn.lock(); | ||
|
|
||
| #[allow(clippy::cast_sign_loss)] | ||
| let total: u64 = conn.query_row( | ||
| "SELECT COUNT(*) FROM sessions WHERE token_hash = ?1", | ||
| params![token_hash], | ||
| |row| row.get::<_, i64>(0), | ||
| )? as u64; | ||
|
|
||
| let mut stmt = conn.prepare( | ||
| "SELECT id, started_at, ended_at, status, message_count, last_activity, metadata | ||
| FROM sessions WHERE token_hash = ?1 | ||
| ORDER BY last_activity DESC LIMIT ?2 OFFSET ?3", |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Handlers mentioning session_id =="
rg -n -C4 --type rust '\bsession_id\b' clients/agent-runtime/src/gateway
echo
echo "== Gateway calls into session/memory APIs =="
rg -n -C4 --type rust '\.mem\.(upsert_session|update_session_activity|store|recall|list|get_session|list_sessions_for_token)\s*\(' clients/agent-runtime/src/gateway
echo
echo "== Token/session ownership logic =="
rg -n -C4 --type rust 'compute_token_hash|token_hash|get_session\(|list_sessions_for_token\(' clients/agent-runtime/src/gatewayRepository: dallay/corvus
Length of output: 50369
🏁 Script executed:
# Check webhook handler's session_id handling and token validation
rg -n -A 30 "async fn handle_webhook\|async fn handle_chat_stream" clients/agent-runtime/src/gateway/mod.rs | head -150Repository: dallay/corvus
Length of output: 39
🏁 Script executed:
# Check if store/recall/list APIs in memory enforce token boundaries
rg -n "fn store\|fn recall\|fn list" clients/agent-runtime/src/memory/sqlite.rs | head -20Repository: dallay/corvus
Length of output: 39
🏁 Script executed:
# Check admin endpoint auth and session filtering
rg -n -B 5 -A 10 "handle_admin_get_session\|handle_admin_list_memory" clients/agent-runtime/src/gateway/admin.rs | grep -A 10 "fn handle_admin"Repository: dallay/corvus
Length of output: 39
🏁 Script executed:
# Inspect list_sessions_for_token implementation and where it's called
rg -n "list_sessions_for_token" clients/agent-runtime/srcRepository: dallay/corvus
Length of output: 866
🏁 Script executed:
# Check sessions.rs context around list_sessions_for_token call
sed -n '40,80p' clients/agent-runtime/src/gateway/sessions.rsRepository: dallay/corvus
Length of output: 1338
🏁 Script executed:
# Examine store/recall/list method signatures and token handling
sed -n '1,100p' clients/agent-runtime/src/memory/traits.rs | tail -50Repository: dallay/corvus
Length of output: 1455
🏁 Script executed:
# Find where store and recall are called in gateway
rg -n "\.store\(|\.recall\(" clients/agent-runtime/src/gateway/mod.rs | head -10Repository: dallay/corvus
Length of output: 314
🏁 Script executed:
# Get context around store calls to see how key is constructed
sed -n '1750,1770p' clients/agent-runtime/src/gateway/mod.rsRepository: dallay/corvus
Length of output: 724
🏁 Script executed:
# Get context around second store call
sed -n '1860,1875p' clients/agent-runtime/src/gateway/mod.rsRepository: dallay/corvus
Length of output: 619
🏁 Script executed:
# Check if session_id from webhook request is validated before use
sed -n '1670,1695p' clients/agent-runtime/src/gateway/mod.rsRepository: dallay/corvus
Length of output: 1138
🏁 Script executed:
# Check what webhook_memory_key() returns
rg -n "fn webhook_memory_key" clients/agent-runtime/src/gateway/mod.rs -A 3Repository: dallay/corvus
Length of output: 303
🏁 Script executed:
# Check what update_session_activity does
rg -n "fn update_session_activity\|async fn update_session_activity" clients/agent-runtime/src/memory/Repository: dallay/corvus
Length of output: 39
🏁 Script executed:
# Check if recall/list in memory validate token ownership
sed -n '200,250p' clients/agent-runtime/src/memory/traits.rsRepository: dallay/corvus
Length of output: 1612
🏁 Script executed:
# Check admin endpoint auth more carefully
sed -n '2110,2160p' clients/agent-runtime/src/gateway/admin.rsRepository: dallay/corvus
Length of output: 1722
🏁 Script executed:
# Check store/recall/list method signatures in memory/sqlite.rs
rg -n "async fn store\|async fn recall\|async fn list" clients/agent-runtime/src/memory/sqlite.rs | grep -v "test\|list_sessions"Repository: dallay/corvus
Length of output: 39
🏁 Script executed:
# Check store implementation
sed -n '600,700p' clients/agent-runtime/src/memory/sqlite.rs | head -50Repository: dallay/corvus
Length of output: 1717
🏁 Script executed:
# Check admin_requires_auth to see if it validates token scope for session_id
rg -n "fn admin_requires_auth" clients/agent-runtime/src/gateway/utils.rs -A 15Repository: dallay/corvus
Length of output: 624
🏁 Script executed:
# Check list() method signature and implementation
rg -n "async fn list" clients/agent-runtime/src/memory/sqlite.rs -A 30 | head -80Repository: dallay/corvus
Length of output: 3303
🏁 Script executed:
# Check recall() implementation
rg -n "async fn recall" clients/agent-runtime/src/memory/sqlite.rs -A 30 | head -60Repository: dallay/corvus
Length of output: 2184
🏁 Script executed:
# Check what filter_by_session does
rg -n "fn filter_by_session" clients/agent-runtime/src/memory/sqlite.rs -A 10Repository: dallay/corvus
Length of output: 398
Verify session_id ownership before any unscoped memory access.
The token-scoped list_sessions_for_token() API is properly protected, but store(), recall(), list(), and update_session_activity() all accept user-supplied session_id without validating that the caller's token owns it. A request can provide an arbitrary session_id (whether from the same token or not), and memory operations will succeed if that session exists—the association recorded during upsert_session() is never validated.
An attacker observing a valid session ID could use it with a different token to read/write that token's conversation history. Admin endpoints have the same issue: they can query any session by ID without checking token ownership.
Add a verification step: before calling store(), recall(), list(), or update_session_activity() with a user-supplied session_id, check that the session exists in the caller's token's session list (list_sessions_for_token()).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@clients/agent-runtime/src/memory/sqlite.rs` around lines 1015 - 1037, Ensure
caller token actually owns a session_id before performing any unscoped memory
access: in the methods that accept user-supplied session IDs (store, recall,
list, update_session_activity—and any admin handlers that currently query by
session id), perform an ownership check by querying whether that session_id is
present for the caller's token (reuse list_sessions_for_token(token_hash, ...)
or a lightweight SELECT COUNT(*) WHERE id = ? AND token_hash = ?). If the check
fails, return an appropriate unauthorized/not-found error and do not call
upsert_session() or perform the memory operation; only proceed when the
session_id belongs to the caller's token (or explicitly allow admin paths that
perform a separate, intentional authorization check).
| /// Create or touch a session record (idempotent). | ||
| async fn upsert_session( | ||
| &self, | ||
| _session_id: &str, | ||
| _token_hash: Option<&str>, | ||
| ) -> anyhow::Result<()> { | ||
| Ok(()) | ||
| } | ||
|
|
||
| /// Mark a session as ended (idempotent — no-op if already ended). | ||
| async fn end_session(&self, _session_id: &str) -> anyhow::Result<()> { | ||
| Ok(()) | ||
| } | ||
|
|
||
| /// Increment message count and update last_activity for an active session. | ||
| async fn update_session_activity(&self, _session_id: &str) -> anyhow::Result<()> { | ||
| Ok(()) | ||
| } | ||
|
|
||
| /// List sessions with optional status filter, pagination, sort, and order. | ||
| async fn list_sessions( | ||
| &self, | ||
| _status: Option<SessionStatus>, | ||
| _limit: u32, | ||
| _offset: u32, | ||
| _sort: &str, | ||
| _order: &str, | ||
| ) -> anyhow::Result<(Vec<SessionEntry>, u64)> { | ||
| Ok((vec![], 0)) | ||
| } | ||
|
|
||
| /// Get a single session by ID. | ||
| async fn get_session(&self, _session_id: &str) -> anyhow::Result<Option<SessionEntry>> { | ||
| Ok(None) | ||
| } | ||
|
|
||
| /// List sessions scoped to a specific token hash, with pagination. | ||
| async fn list_sessions_for_token( | ||
| &self, | ||
| _token_hash: &str, | ||
| _limit: u32, | ||
| _offset: u32, | ||
| ) -> anyhow::Result<(Vec<SessionEntry>, u64)> { | ||
| Ok((vec![], 0)) | ||
| } | ||
|
|
||
| /// Return aggregated memory and session statistics. | ||
| async fn memory_stats(&self) -> anyhow::Result<MemoryStats> { | ||
| Ok(MemoryStats::default()) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
fd -e rs . clients/agent-runtime/src/memory | while read -r file; do
if rg -n 'impl\s+Memory\s+for\s+' "$file" >/dev/null; then
echo "== $file =="
rg -n -C1 'impl\s+Memory\s+for\s+|async fn (upsert_session|end_session|update_session_activity|list_sessions|get_session|list_sessions_for_token|memory_stats)\b' "$file"
echo
fi
doneRepository: dallay/corvus
Length of output: 1327
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check if Lucid backend has explicit session/stats method implementations
echo "=== LucidMemory ==="
rg -n "async fn (upsert_session|end_session|update_session_activity|list_sessions|get_session|list_sessions_for_token|memory_stats)" clients/agent-runtime/src/memory/lucid.rs || echo "No explicit methods found"
echo ""
echo "=== MarkdownMemory ==="
rg -n "async fn (upsert_session|end_session|update_session_activity|list_sessions|get_session|list_sessions_for_token|memory_stats)" clients/agent-runtime/src/memory/markdown.rs || echo "No explicit methods found"
echo ""
echo "=== NoneMemory ==="
rg -n "async fn (upsert_session|end_session|update_session_activity|list_sessions|get_session|list_sessions_for_token|memory_stats)" clients/agent-runtime/src/memory/none.rs || echo "No explicit methods found"
echo ""
echo "=== Checking trait default implementations in traits.rs ==="
rg -n "async fn (upsert_session|end_session|update_session_activity|list_sessions|get_session|list_sessions_for_token|memory_stats)" clients/agent-runtime/src/memory/traits.rs -A 5Repository: dallay/corvus
Length of output: 1575
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Checking for gateway/endpoint usage of session/stats APIs ==="
rg -l "(upsert_session|end_session|update_session_activity|list_sessions|get_session|list_sessions_for_token|memory_stats)" clients/agent-runtime/src --type rs | grep -v "/memory/" | head -20
echo ""
echo "=== Checking for tests of session/stats behavior ==="
fd -e rs clients/agent-runtime/src/memory --type f | xargs grep -l "test.*session\|session.*test" 2>/dev/null | head -10
echo ""
echo "=== Checking NoneMemory usage/intent ==="
rg -n "NoneMemory" clients/agent-runtime/src --type rs -B2 -A2 | head -30Repository: dallay/corvus
Length of output: 166
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Checking for gateway/endpoint usage of session/stats APIs ==="
rg "(upsert_session|end_session|update_session_activity|list_sessions|get_session|list_sessions_for_token|memory_stats)" clients/agent-runtime/src -l | grep -v "/memory/" | head -20
echo ""
echo "=== Checking for tests of session/stats behavior ==="
fd -e rs clients/agent-runtime/src/memory -type f | xargs grep -l "test.*session\|session.*test" 2>/dev/null | head -10
echo ""
echo "=== Checking NoneMemory usage/intent ==="
rg "NoneMemory" clients/agent-runtime/src -B2 -A2 | head -40Repository: dallay/corvus
Length of output: 496
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Checking gateway/sessions.rs for session API usage ==="
rg -n "upsert_session|end_session|update_session_activity|list_sessions|get_session|list_sessions_for_token|memory_stats" clients/agent-runtime/src/gateway/sessions.rs -B3 -A3
echo ""
echo "=== Checking gateway/admin.rs for memory_stats usage ==="
rg -n "memory_stats" clients/agent-runtime/src/gateway/admin.rs -B3 -A3
echo ""
echo "=== Checking for test coverage ==="
find clients/agent-runtime/src -name "*test*" -o -name "*spec*" | head -20
echo ""
echo "=== Searching for #[test] or #[tokio::test] ==="
rg -l "#\[.*test\]" clients/agent-runtime/src | head -10Repository: dallay/corvus
Length of output: 2041
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Checking which backends are instantiated in code ==="
rg "Memory::|LucidMemory|MarkdownMemory|NoneMemory|SqliteMemory" clients/agent-runtime/src -B2 -A2 | head -60
echo ""
echo "=== Checking test state creation to see which backends are tested ==="
rg -n "test_state|TempDir" clients/agent-runtime/src/gateway/admin.rs -A5 | head -40
echo ""
echo "=== Check if there's conditional logic based on backend type ==="
rg "name\(\)|match.*name" clients/agent-runtime/src/gateway -B2 -A5Repository: dallay/corvus
Length of output: 7775
Three production backends silently return empty data for session/stats APIs instead of signaling unsupported.
LucidMemory, MarkdownMemory, and NoneMemory do not implement the new upsert_session, end_session, update_session_activity, list_sessions, get_session, list_sessions_for_token, and memory_stats methods. They fall through to trait defaults that return success with empty data. The gateway endpoints in admin.rs and sessions.rs call these methods directly without backend type checks, so users selecting Lucid or Markdown backends will receive 200 OK with empty sessions/stats instead of a clear "unsupported" error.
Options:
- Implement these methods in Lucid/Markdown/None backends (if intended to support sessions) or
- Return
Err(anyhow!("sessions unsupported"))from trait defaults so unsupported backends fail explicitly, or - Add gateway-level checks to reject session endpoints for non-SQLite backends.
Also add test coverage for non-SQLite backends with these APIs; currently all session tests use test_state_with_sqlite() only.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@clients/agent-runtime/src/memory/traits.rs` around lines 171 - 220, The trait
default implementations for upsert_session, end_session,
update_session_activity, list_sessions, get_session, list_sessions_for_token,
and memory_stats silently return success/empty results causing LucidMemory,
MarkdownMemory, and NoneMemory to appear to support sessions; change the trait
defaults in traits.rs to return Err(anyhow!("sessions unsupported")) so
unsupported backends fail fast, and/or implement these methods on
LucidMemory/MarkdownMemory/NoneMemory if they should support sessions;
additionally, update gateway callers in admin.rs and sessions.rs to either check
the backend type (reject non-SQLite backends) or propagate and translate the
error into a 4xx response, and add tests that call these session/stats APIs
against non-SQLite backends to assert they return the unsupported error.
| #### Scenario: Dashboard displays session list | ||
| - GIVEN the admin user is authenticated in the dashboard | ||
| - AND 5 active and 3 ended sessions exist | ||
| - WHEN the admin navigates to the session monitoring page | ||
| - THEN a table MUST display 8 rows | ||
| - AND each row MUST show: session ID, started timestamp, last activity, message count, status | ||
| - AND active sessions MUST be visually distinct from ended sessions. | ||
|
|
||
| #### Scenario: Filter sessions by active status | ||
| - GIVEN the admin is on the session monitoring page | ||
| - AND 5 active and 3 ended sessions exist | ||
| - WHEN the admin selects the "Active" status filter | ||
| - THEN the table MUST display only the 5 active sessions | ||
| - AND ended sessions MUST NOT appear. | ||
|
|
||
| ### Requirement: Dashboard Session Detail View (CS-2) | ||
|
|
||
| The dashboard MUST provide a session detail panel accessible by clicking a session row. | ||
|
|
||
| - The detail view MUST display: session ID, started_at, ended_at (or "Active"), message_count, last_activity, metadata (if present). | ||
| - The detail view MUST display a memory summary: count of memory entries by category for that session. | ||
| - The detail view SHOULD provide a link/button to view the session's memory entries in the memory browser (pre-filtered by session_id). | ||
| - The detail view MUST consume `GET /web/admin/sessions/:id` from the gateway. | ||
|
|
||
| #### Scenario: Admin views session detail | ||
| - GIVEN session "abc-123" is active with 15 messages and 6 memory entries (4 Conversation, 2 Core) | ||
| - WHEN the admin clicks session "abc-123" in the session list | ||
| - THEN the session detail panel MUST open | ||
| - AND it MUST show: id "abc-123", message count 15, status "Active" | ||
| - AND it MUST show memory summary: Conversation 4, Core 2. | ||
|
|
||
| ### Requirement: Dashboard Memory Browser (CS-3) | ||
|
|
||
| The dashboard MUST include a memory administration page with a searchable, filterable list of memory entries. | ||
|
|
||
| - The page MUST be accessible from the dashboard navigation. | ||
| - The page MUST display a table/list of memory entries with columns: Key, Category, Timestamp, Session ID, Content (truncated preview). | ||
| - The page MUST support filtering by category (Core, Daily, Conversation, Custom). | ||
| - The page MUST support filtering by session ID (dropdown or text input). | ||
| - The page MUST support full-text search via a search input field. | ||
| - The page MUST support pagination. | ||
| - Each entry MUST have a "Delete" action (with confirmation dialog). | ||
| - The page MUST consume `GET /web/admin/memory` and `DELETE /web/admin/memory/:key`. | ||
|
|
||
| #### Scenario: Admin deletes a memory entry | ||
| - GIVEN a memory entry with key "outdated-fact" exists in the browser | ||
| - WHEN the admin clicks "Delete" on that entry | ||
| - THEN a confirmation dialog MUST appear | ||
| - WHEN the admin confirms deletion | ||
| - THEN the entry MUST be removed from the list | ||
| - AND a DELETE request MUST be sent to /web/admin/memory/outdated-fact. | ||
|
|
||
| ### Requirement: Dashboard Memory Stats Summary (CS-4) | ||
|
|
||
| The dashboard memory browser page MUST display a stats summary panel. | ||
|
|
||
| - The panel MUST show: total entry count, entries by category, total sessions, active sessions, backend name, Cerebro status. | ||
| - The panel MUST consume `GET /web/admin/memory/stats`. | ||
| - The panel SHOULD be displayed above or alongside the memory entry list. | ||
|
|
||
| #### Scenario: Memory stats panel displays correctly | ||
| - GIVEN 50 memory entries (20 Core, 15 Conversation, 10 Daily, 5 Custom) | ||
| - AND 8 total sessions, 3 active | ||
| - AND backend is "sqlite", Cerebro is not configured | ||
| - WHEN the admin views the memory browser page | ||
| - THEN the stats panel MUST show: 50 total entries | ||
| - AND the panel MUST show category breakdown | ||
| - AND the panel MUST show 8 total sessions, 3 active | ||
| - AND the panel MUST show backend "sqlite" | ||
| - AND the panel MUST show Cerebro as "Not configured". | ||
|
|
||
| ### Requirement: Chat Session History Sidebar (CS-5) | ||
|
|
||
| The chat app MUST include a collapsible session history sidebar. | ||
|
|
||
| - The sidebar MUST list past sessions from `GET /session/list`. | ||
| - Each session entry MUST display: session start time (relative or absolute) and message count. | ||
| - The current active session MUST be visually highlighted. | ||
| - Clicking a past session MUST switch the chat to that session's context. | ||
| - The sidebar MUST include a "New Chat" action that creates a new session. | ||
| - The sidebar MUST be collapsible to preserve chat viewport space. | ||
| - The sidebar MUST NOT display memory contents, keys, or categories. | ||
|
|
||
| #### Scenario: Chat sidebar lists past sessions | ||
| - GIVEN the user is authenticated in the chat app | ||
| - AND the user has 4 past sessions and 1 current active session | ||
| - WHEN the chat app loads | ||
| - THEN the sidebar MUST display 5 session entries | ||
| - AND the current session MUST be visually highlighted | ||
| - AND each entry MUST show start time and message count. | ||
|
|
||
| #### Scenario: User switches to a past session | ||
| - GIVEN the sidebar shows sessions including "sess-old" with 12 messages | ||
| - WHEN the user clicks "sess-old" in the sidebar | ||
| - THEN the chat MUST load the context for session "sess-old" | ||
| - AND the X-Session-Id header MUST be set to "sess-old" for subsequent requests | ||
| - AND "sess-old" MUST become the highlighted session. | ||
|
|
||
| ### Requirement: Chat Session Data Persistence (CS-6) | ||
|
|
||
| Chat session context MUST be persisted across page reloads. | ||
|
|
||
| - The current session ID MUST be stored in `sessionStorage` (existing behavior). | ||
| - The session list MUST be fetched from the server via `GET /session/list` on load. | ||
| - Messages for the current session MUST continue to use `sessionStorage` persistence (existing behavior). | ||
| - When switching sessions, the current session's messages MUST be saved to `sessionStorage` before loading the new session. | ||
|
|
||
| #### Scenario: Session list loads from server on mount | ||
| - GIVEN the user is authenticated | ||
| - AND the user has 3 past sessions on the server | ||
| - WHEN the chat app mounts | ||
| - THEN useChat MUST call GET /session/list | ||
| - AND the sidebar MUST populate with the 3 sessions plus the current session. | ||
|
|
||
| ### Requirement: Chat No Memory Visibility (CS-7) | ||
|
|
||
| The chat app MUST NOT expose raw memory contents to end users. | ||
|
|
||
| - The chat MUST NOT display memory keys, categories, or raw content. | ||
| - The chat MAY display subtle "context used" indicators as a future enhancement — this is NOT required for Phase 1. | ||
| - The chat MUST NOT call any `/web/admin/memory*` endpoint. | ||
|
|
||
| #### Scenario: Chat does not expose memory data | ||
| - GIVEN session "abc-123" has 10 associated memory entries | ||
| - WHEN the user is chatting in session "abc-123" | ||
| - THEN no memory entries, keys, or categories MUST be visible in the chat UI | ||
| - AND no requests to /web/admin/memory endpoints MUST be made. | ||
|
|
||
| ### Requirement: Dashboard Admin TypeScript Types (CS-8) | ||
|
|
||
| The dashboard MUST define TypeScript types for all new API responses. | ||
|
|
||
| - `AdminSessionView`: session list item (id, started_at, ended_at, message_count, last_activity). | ||
| - `AdminSessionDetail`: extends `AdminSessionView` with metadata and memory_summary. | ||
| - `AdminMemoryEntry`: memory entry (id, key, content, category, timestamp, session_id). | ||
| - `AdminMemoryStats`: stats response (total_entries, by_category, total_sessions, active_sessions, backend, cerebro_configured). | ||
| - Types MUST be defined in the existing `admin-config.ts` or a co-located types file. | ||
|
|
||
| #### Scenario: TypeScript types match API response shape | ||
| - GIVEN the dashboard makes a request to GET /web/admin/sessions | ||
| - WHEN the response is received | ||
| - THEN the response MUST be parseable as PaginatedResponse<AdminSessionView> | ||
| - AND all fields defined in AdminSessionView MUST be present. | ||
|
|
||
| ### Requirement: KMP/Mobile Session Visibility Deferred (CS-9) | ||
|
|
||
| KMP and mobile clients (composeApp, androidApp) are OUT OF SCOPE for Phase 1. | ||
|
|
||
| - Session history and memory visibility for KMP clients MUST NOT be implemented in this change. | ||
| - The KMP `CoreContracts.kt` MAY be updated with session history type stubs if convenient, but this is NOT required. | ||
| - The mobile bridge is not wired — session history depends on bridge completion (tracked separately). | ||
|
|
||
| #### Scenario: KMP contracts remain unchanged | ||
| - GIVEN the KMP module CoreContracts.kt | ||
| - WHEN this change is implemented | ||
| - THEN CoreContracts.kt MUST NOT be modified unless adding optional type stubs | ||
| - AND existing KMP functionality MUST NOT be affected. | ||
|
|
||
| ### Requirement: Visibility Rules (CS-10) | ||
|
|
||
| | Capability | Dashboard (Admin) | Chat (End-User) | KMP/Mobile | | ||
| |------------|-------------------|------------------|------------| | ||
| | Session list (all) | MUST | - | Deferred | | ||
| | Session list (own) | - | MUST | Deferred | | ||
| | Session detail | MUST | - | Deferred | | ||
| | Memory browser | MUST | MUST NOT | Deferred | | ||
| | Memory stats | MUST | MUST NOT | Deferred | | ||
| | Memory delete | MUST | MUST NOT | Deferred | | ||
| | Memory search | MUST | MUST NOT | Deferred | | ||
| | Session switching | - | MUST | Deferred | | ||
| | New chat / session | - | MUST | Deferred | | ||
|
|
||
| #### Scenario: Admin has full visibility | ||
| - GIVEN an admin user authenticated in the dashboard | ||
| - THEN the user MUST have access to: session list, session detail, memory browser, memory stats, memory delete. | ||
|
|
||
| #### Scenario: End-user has scoped visibility |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Minor: Add blank lines before scenario headings (MD022).
Static analysis flagged missing blank lines before #### Scenario: headings. This is a minor formatting nit.
Example fix pattern
- The view MUST consume `GET /web/admin/sessions` from the gateway.
+
#### Scenario: Dashboard displays session list📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| #### Scenario: Dashboard displays session list | |
| - GIVEN the admin user is authenticated in the dashboard | |
| - AND 5 active and 3 ended sessions exist | |
| - WHEN the admin navigates to the session monitoring page | |
| - THEN a table MUST display 8 rows | |
| - AND each row MUST show: session ID, started timestamp, last activity, message count, status | |
| - AND active sessions MUST be visually distinct from ended sessions. | |
| #### Scenario: Filter sessions by active status | |
| - GIVEN the admin is on the session monitoring page | |
| - AND 5 active and 3 ended sessions exist | |
| - WHEN the admin selects the "Active" status filter | |
| - THEN the table MUST display only the 5 active sessions | |
| - AND ended sessions MUST NOT appear. | |
| ### Requirement: Dashboard Session Detail View (CS-2) | |
| The dashboard MUST provide a session detail panel accessible by clicking a session row. | |
| - The detail view MUST display: session ID, started_at, ended_at (or "Active"), message_count, last_activity, metadata (if present). | |
| - The detail view MUST display a memory summary: count of memory entries by category for that session. | |
| - The detail view SHOULD provide a link/button to view the session's memory entries in the memory browser (pre-filtered by session_id). | |
| - The detail view MUST consume `GET /web/admin/sessions/:id` from the gateway. | |
| #### Scenario: Admin views session detail | |
| - GIVEN session "abc-123" is active with 15 messages and 6 memory entries (4 Conversation, 2 Core) | |
| - WHEN the admin clicks session "abc-123" in the session list | |
| - THEN the session detail panel MUST open | |
| - AND it MUST show: id "abc-123", message count 15, status "Active" | |
| - AND it MUST show memory summary: Conversation 4, Core 2. | |
| ### Requirement: Dashboard Memory Browser (CS-3) | |
| The dashboard MUST include a memory administration page with a searchable, filterable list of memory entries. | |
| - The page MUST be accessible from the dashboard navigation. | |
| - The page MUST display a table/list of memory entries with columns: Key, Category, Timestamp, Session ID, Content (truncated preview). | |
| - The page MUST support filtering by category (Core, Daily, Conversation, Custom). | |
| - The page MUST support filtering by session ID (dropdown or text input). | |
| - The page MUST support full-text search via a search input field. | |
| - The page MUST support pagination. | |
| - Each entry MUST have a "Delete" action (with confirmation dialog). | |
| - The page MUST consume `GET /web/admin/memory` and `DELETE /web/admin/memory/:key`. | |
| #### Scenario: Admin deletes a memory entry | |
| - GIVEN a memory entry with key "outdated-fact" exists in the browser | |
| - WHEN the admin clicks "Delete" on that entry | |
| - THEN a confirmation dialog MUST appear | |
| - WHEN the admin confirms deletion | |
| - THEN the entry MUST be removed from the list | |
| - AND a DELETE request MUST be sent to /web/admin/memory/outdated-fact. | |
| ### Requirement: Dashboard Memory Stats Summary (CS-4) | |
| The dashboard memory browser page MUST display a stats summary panel. | |
| - The panel MUST show: total entry count, entries by category, total sessions, active sessions, backend name, Cerebro status. | |
| - The panel MUST consume `GET /web/admin/memory/stats`. | |
| - The panel SHOULD be displayed above or alongside the memory entry list. | |
| #### Scenario: Memory stats panel displays correctly | |
| - GIVEN 50 memory entries (20 Core, 15 Conversation, 10 Daily, 5 Custom) | |
| - AND 8 total sessions, 3 active | |
| - AND backend is "sqlite", Cerebro is not configured | |
| - WHEN the admin views the memory browser page | |
| - THEN the stats panel MUST show: 50 total entries | |
| - AND the panel MUST show category breakdown | |
| - AND the panel MUST show 8 total sessions, 3 active | |
| - AND the panel MUST show backend "sqlite" | |
| - AND the panel MUST show Cerebro as "Not configured". | |
| ### Requirement: Chat Session History Sidebar (CS-5) | |
| The chat app MUST include a collapsible session history sidebar. | |
| - The sidebar MUST list past sessions from `GET /session/list`. | |
| - Each session entry MUST display: session start time (relative or absolute) and message count. | |
| - The current active session MUST be visually highlighted. | |
| - Clicking a past session MUST switch the chat to that session's context. | |
| - The sidebar MUST include a "New Chat" action that creates a new session. | |
| - The sidebar MUST be collapsible to preserve chat viewport space. | |
| - The sidebar MUST NOT display memory contents, keys, or categories. | |
| #### Scenario: Chat sidebar lists past sessions | |
| - GIVEN the user is authenticated in the chat app | |
| - AND the user has 4 past sessions and 1 current active session | |
| - WHEN the chat app loads | |
| - THEN the sidebar MUST display 5 session entries | |
| - AND the current session MUST be visually highlighted | |
| - AND each entry MUST show start time and message count. | |
| #### Scenario: User switches to a past session | |
| - GIVEN the sidebar shows sessions including "sess-old" with 12 messages | |
| - WHEN the user clicks "sess-old" in the sidebar | |
| - THEN the chat MUST load the context for session "sess-old" | |
| - AND the X-Session-Id header MUST be set to "sess-old" for subsequent requests | |
| - AND "sess-old" MUST become the highlighted session. | |
| ### Requirement: Chat Session Data Persistence (CS-6) | |
| Chat session context MUST be persisted across page reloads. | |
| - The current session ID MUST be stored in `sessionStorage` (existing behavior). | |
| - The session list MUST be fetched from the server via `GET /session/list` on load. | |
| - Messages for the current session MUST continue to use `sessionStorage` persistence (existing behavior). | |
| - When switching sessions, the current session's messages MUST be saved to `sessionStorage` before loading the new session. | |
| #### Scenario: Session list loads from server on mount | |
| - GIVEN the user is authenticated | |
| - AND the user has 3 past sessions on the server | |
| - WHEN the chat app mounts | |
| - THEN useChat MUST call GET /session/list | |
| - AND the sidebar MUST populate with the 3 sessions plus the current session. | |
| ### Requirement: Chat No Memory Visibility (CS-7) | |
| The chat app MUST NOT expose raw memory contents to end users. | |
| - The chat MUST NOT display memory keys, categories, or raw content. | |
| - The chat MAY display subtle "context used" indicators as a future enhancement — this is NOT required for Phase 1. | |
| - The chat MUST NOT call any `/web/admin/memory*` endpoint. | |
| #### Scenario: Chat does not expose memory data | |
| - GIVEN session "abc-123" has 10 associated memory entries | |
| - WHEN the user is chatting in session "abc-123" | |
| - THEN no memory entries, keys, or categories MUST be visible in the chat UI | |
| - AND no requests to /web/admin/memory endpoints MUST be made. | |
| ### Requirement: Dashboard Admin TypeScript Types (CS-8) | |
| The dashboard MUST define TypeScript types for all new API responses. | |
| - `AdminSessionView`: session list item (id, started_at, ended_at, message_count, last_activity). | |
| - `AdminSessionDetail`: extends `AdminSessionView` with metadata and memory_summary. | |
| - `AdminMemoryEntry`: memory entry (id, key, content, category, timestamp, session_id). | |
| - `AdminMemoryStats`: stats response (total_entries, by_category, total_sessions, active_sessions, backend, cerebro_configured). | |
| - Types MUST be defined in the existing `admin-config.ts` or a co-located types file. | |
| #### Scenario: TypeScript types match API response shape | |
| - GIVEN the dashboard makes a request to GET /web/admin/sessions | |
| - WHEN the response is received | |
| - THEN the response MUST be parseable as PaginatedResponse<AdminSessionView> | |
| - AND all fields defined in AdminSessionView MUST be present. | |
| ### Requirement: KMP/Mobile Session Visibility Deferred (CS-9) | |
| KMP and mobile clients (composeApp, androidApp) are OUT OF SCOPE for Phase 1. | |
| - Session history and memory visibility for KMP clients MUST NOT be implemented in this change. | |
| - The KMP `CoreContracts.kt` MAY be updated with session history type stubs if convenient, but this is NOT required. | |
| - The mobile bridge is not wired — session history depends on bridge completion (tracked separately). | |
| #### Scenario: KMP contracts remain unchanged | |
| - GIVEN the KMP module CoreContracts.kt | |
| - WHEN this change is implemented | |
| - THEN CoreContracts.kt MUST NOT be modified unless adding optional type stubs | |
| - AND existing KMP functionality MUST NOT be affected. | |
| ### Requirement: Visibility Rules (CS-10) | |
| | Capability | Dashboard (Admin) | Chat (End-User) | KMP/Mobile | | |
| |------------|-------------------|------------------|------------| | |
| | Session list (all) | MUST | - | Deferred | | |
| | Session list (own) | - | MUST | Deferred | | |
| | Session detail | MUST | - | Deferred | | |
| | Memory browser | MUST | MUST NOT | Deferred | | |
| | Memory stats | MUST | MUST NOT | Deferred | | |
| | Memory delete | MUST | MUST NOT | Deferred | | |
| | Memory search | MUST | MUST NOT | Deferred | | |
| | Session switching | - | MUST | Deferred | | |
| | New chat / session | - | MUST | Deferred | | |
| #### Scenario: Admin has full visibility | |
| - GIVEN an admin user authenticated in the dashboard | |
| - THEN the user MUST have access to: session list, session detail, memory browser, memory stats, memory delete. | |
| #### Scenario: End-user has scoped visibility | |
| #### Scenario: Dashboard displays session list | |
| - GIVEN the admin user is authenticated in the dashboard | |
| - AND 5 active and 3 ended sessions exist | |
| - WHEN the admin navigates to the session monitoring page | |
| - THEN a table MUST display 8 rows | |
| - AND each row MUST show: session ID, started timestamp, last activity, message count, status | |
| - AND active sessions MUST be visually distinct from ended sessions. | |
| #### Scenario: Filter sessions by active status | |
| - GIVEN the admin is on the session monitoring page | |
| - AND 5 active and 3 ended sessions exist | |
| - WHEN the admin selects the "Active" status filter | |
| - THEN the table MUST display only the 5 active sessions | |
| - AND ended sessions MUST NOT appear. | |
| ### Requirement: Dashboard Session Detail View (CS-2) | |
| The dashboard MUST provide a session detail panel accessible by clicking a session row. | |
| - The detail view MUST display: session ID, started_at, ended_at (or "Active"), message_count, last_activity, metadata (if present). | |
| - The detail view MUST display a memory summary: count of memory entries by category for that session. | |
| - The detail view SHOULD provide a link/button to view the session's memory entries in the memory browser (pre-filtered by session_id). | |
| - The detail view MUST consume `GET /web/admin/sessions/:id` from the gateway. | |
| #### Scenario: Admin views session detail | |
| - GIVEN session "abc-123" is active with 15 messages and 6 memory entries (4 Conversation, 2 Core) | |
| - WHEN the admin clicks session "abc-123" in the session list | |
| - THEN the session detail panel MUST open | |
| - AND it MUST show: id "abc-123", message count 15, status "Active" | |
| - AND it MUST show memory summary: Conversation 4, Core 2. | |
| ### Requirement: Dashboard Memory Browser (CS-3) | |
| The dashboard MUST include a memory administration page with a searchable, filterable list of memory entries. | |
| - The page MUST be accessible from the dashboard navigation. | |
| - The page MUST display a table/list of memory entries with columns: Key, Category, Timestamp, Session ID, Content (truncated preview). | |
| - The page MUST support filtering by category (Core, Daily, Conversation, Custom). | |
| - The page MUST support filtering by session ID (dropdown or text input). | |
| - The page MUST support full-text search via a search input field. | |
| - The page MUST support pagination. | |
| - Each entry MUST have a "Delete" action (with confirmation dialog). | |
| - The page MUST consume `GET /web/admin/memory` and `DELETE /web/admin/memory/:key`. | |
| #### Scenario: Admin deletes a memory entry | |
| - GIVEN a memory entry with key "outdated-fact" exists in the browser | |
| - WHEN the admin clicks "Delete" on that entry | |
| - THEN a confirmation dialog MUST appear | |
| - WHEN the admin confirms deletion | |
| - THEN the entry MUST be removed from the list | |
| - AND a DELETE request MUST be sent to /web/admin/memory/outdated-fact. | |
| ### Requirement: Dashboard Memory Stats Summary (CS-4) | |
| The dashboard memory browser page MUST display a stats summary panel. | |
| - The panel MUST show: total entry count, entries by category, total sessions, active sessions, backend name, Cerebro status. | |
| - The panel MUST consume `GET /web/admin/memory/stats`. | |
| - The panel SHOULD be displayed above or alongside the memory entry list. | |
| #### Scenario: Memory stats panel displays correctly | |
| - GIVEN 50 memory entries (20 Core, 15 Conversation, 10 Daily, 5 Custom) | |
| - AND 8 total sessions, 3 active | |
| - AND backend is "sqlite", Cerebro is not configured | |
| - WHEN the admin views the memory browser page | |
| - THEN the stats panel MUST show: 50 total entries | |
| - AND the panel MUST show category breakdown | |
| - AND the panel MUST show 8 total sessions, 3 active | |
| - AND the panel MUST show backend "sqlite" | |
| - AND the panel MUST show Cerebro as "Not configured". | |
| ### Requirement: Chat Session History Sidebar (CS-5) | |
| The chat app MUST include a collapsible session history sidebar. | |
| - The sidebar MUST list past sessions from `GET /session/list`. | |
| - Each session entry MUST display: session start time (relative or absolute) and message count. | |
| - The current active session MUST be visually highlighted. | |
| - Clicking a past session MUST switch the chat to that session's context. | |
| - The sidebar MUST include a "New Chat" action that creates a new session. | |
| - The sidebar MUST be collapsible to preserve chat viewport space. | |
| - The sidebar MUST NOT display memory contents, keys, or categories. | |
| #### Scenario: Chat sidebar lists past sessions | |
| - GIVEN the user is authenticated in the chat app | |
| - AND the user has 4 past sessions and 1 current active session | |
| - WHEN the chat app loads | |
| - THEN the sidebar MUST display 5 session entries | |
| - AND the current session MUST be visually highlighted | |
| - AND each entry MUST show start time and message count. | |
| #### Scenario: User switches to a past session | |
| - GIVEN the sidebar shows sessions including "sess-old" with 12 messages | |
| - WHEN the user clicks "sess-old" in the sidebar | |
| - THEN the chat MUST load the context for session "sess-old" | |
| - AND the X-Session-Id header MUST be set to "sess-old" for subsequent requests | |
| - AND "sess-old" MUST become the highlighted session. | |
| ### Requirement: Chat Session Data Persistence (CS-6) | |
| Chat session context MUST be persisted across page reloads. | |
| - The current session ID MUST be stored in `sessionStorage` (existing behavior). | |
| - The session list MUST be fetched from the server via `GET /session/list` on load. | |
| - Messages for the current session MUST continue to use `sessionStorage` persistence (existing behavior). | |
| - When switching sessions, the current session's messages MUST be saved to `sessionStorage` before loading the new session. | |
| #### Scenario: Session list loads from server on mount | |
| - GIVEN the user is authenticated | |
| - AND the user has 3 past sessions on the server | |
| - WHEN the chat app mounts | |
| - THEN useChat MUST call GET /session/list | |
| - AND the sidebar MUST populate with the 3 sessions plus the current session. | |
| ### Requirement: Chat No Memory Visibility (CS-7) | |
| The chat app MUST NOT expose raw memory contents to end users. | |
| - The chat MUST NOT display memory keys, categories, or raw content. | |
| - The chat MAY display subtle "context used" indicators as a future enhancement — this is NOT required for Phase 1. | |
| - The chat MUST NOT call any `/web/admin/memory*` endpoint. | |
| #### Scenario: Chat does not expose memory data | |
| - GIVEN session "abc-123" has 10 associated memory entries | |
| - WHEN the user is chatting in session "abc-123" | |
| - THEN no memory entries, keys, or categories MUST be visible in the chat UI | |
| - AND no requests to /web/admin/memory endpoints MUST be made. | |
| ### Requirement: Dashboard Admin TypeScript Types (CS-8) | |
| The dashboard MUST define TypeScript types for all new API responses. | |
| - `AdminSessionView`: session list item (id, started_at, ended_at, message_count, last_activity). | |
| - `AdminSessionDetail`: extends `AdminSessionView` with metadata and memory_summary. | |
| - `AdminMemoryEntry`: memory entry (id, key, content, category, timestamp, session_id). | |
| - `AdminMemoryStats`: stats response (total_entries, by_category, total_sessions, active_sessions, backend, cerebro_configured). | |
| - Types MUST be defined in the existing `admin-config.ts` or a co-located types file. | |
| #### Scenario: TypeScript types match API response shape | |
| - GIVEN the dashboard makes a request to GET /web/admin/sessions | |
| - WHEN the response is received | |
| - THEN the response MUST be parseable as PaginatedResponse<AdminSessionView> | |
| - AND all fields defined in AdminSessionView MUST be present. | |
| ### Requirement: KMP/Mobile Session Visibility Deferred (CS-9) | |
| KMP and mobile clients (composeApp, androidApp) are OUT OF SCOPE for Phase 1. | |
| - Session history and memory visibility for KMP clients MUST NOT be implemented in this change. | |
| - The KMP `CoreContracts.kt` MAY be updated with session history type stubs if convenient, but this is NOT required. | |
| - The mobile bridge is not wired — session history depends on bridge completion (tracked separately). | |
| #### Scenario: KMP contracts remain unchanged | |
| - GIVEN the KMP module CoreContracts.kt | |
| - WHEN this change is implemented | |
| - THEN CoreContracts.kt MUST NOT be modified unless adding optional type stubs | |
| - AND existing KMP functionality MUST NOT be affected. | |
| ### Requirement: Visibility Rules (CS-10) | |
| | Capability | Dashboard (Admin) | Chat (End-User) | KMP/Mobile | | |
| |------------|-------------------|------------------|------------| | |
| | Session list (all) | MUST | - | Deferred | | |
| | Session list (own) | - | MUST | Deferred | | |
| | Session detail | MUST | - | Deferred | | |
| | Memory browser | MUST | MUST NOT | Deferred | | |
| | Memory stats | MUST | MUST NOT | Deferred | | |
| | Memory delete | MUST | MUST NOT | Deferred | | |
| | Memory search | MUST | MUST NOT | Deferred | | |
| | Session switching | - | MUST | Deferred | | |
| | New chat / session | - | MUST | Deferred | | |
| #### Scenario: Admin has full visibility | |
| - GIVEN an admin user authenticated in the dashboard | |
| - THEN the user MUST have access to: session list, session detail, memory browser, memory stats, memory delete. | |
| #### Scenario: End-user has scoped visibility |
🧰 Tools
🪛 markdownlint-cli2 (0.22.0)
[warning] 305-305: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 313-313: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 329-329: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 349-349: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 365-365: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 388-388: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 396-396: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 412-412: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 427-427: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 443-443: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 457-457: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 477-477: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 481-481: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@openspec/specs/client-surfaces/spec.md` around lines 305 - 481, MD022: Add a
single blank line immediately before each "#### Scenario:" heading to satisfy
the markdown lint rule; update the occurrences such as "#### Scenario: Dashboard
displays session list", "#### Scenario: Filter sessions by active status", "####
Scenario: Admin views session detail", "#### Scenario: Admin deletes a memory
entry", "#### Scenario: Memory stats panel displays correctly", "#### Scenario:
Chat sidebar lists past sessions", "#### Scenario: User switches to a past
session", "#### Scenario: Session list loads from server on mount", and "####
Scenario: Chat does not expose memory data" by inserting one blank line above
each heading in the spec.md content so every scenario block is separated from
the previous paragraph/heading.
de8ae56 to
640a8c1
Compare
There was a problem hiding this comment.
Actionable comments posted: 23
♻️ Duplicate comments (10)
clients/web/apps/chat/src/composables/useGateway.spec.ts (1)
495-499:⚠️ Potential issue | 🟠 MajorQuery parameter mismatch between frontend and backend.
The test asserts
page=1&per_page=20, but the backendSessionListParamsstruct (ingateway/sessions.rs) expectslimitandoffset— notpageandper_page. The backend will receiveNonefor both and fall back to defaults, ignoring the caller's pagination intent.Either the frontend URL construction or backend param names need alignment.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@clients/web/apps/chat/src/composables/useGateway.spec.ts` around lines 495 - 499, The test (in useGateway.spec.ts) is asserting query params "page" and "per_page" but the backend expects "limit" and "offset" (SessionListParams), so update the frontend URL construction or the test accordingly: locate where the sessions list URL is built (the code under test that triggers fetch — look for the function that calls fetch and names like "/session/list" or the test's subject) and change the query parameter names from page/per_page to limit/offset (or convert page/per_page to computed limit and offset) so the request matches SessionListParams; then update the spec expectations to assert "limit" and "offset" (or the computed numeric values) and keep the Authorization header assertion as-is.clients/web/apps/chat/src/App.vue (1)
261-285:⚠️ Potential issue | 🟠 MajorReject the whole persisted payload unless every message is valid.
This still accepts partially invalid arrays and any numeric
id.NaN/Infinitypasstypeof === "number"and will turnmessageIdCounterintoNaN, while mixed arrays silently drop bad entries instead of resetting the session.🛡️ Tighten the restore guard
- const parsed = JSON.parse(raw) as unknown[]; - if (!Array.isArray(parsed) || parsed.length === 0) { + const parsed = JSON.parse(raw); + const isPersistedMessage = (value: unknown): value is Message => + value !== null && + typeof value === "object" && + Number.isFinite((value as Record<string, unknown>).id) && + ((value as Record<string, unknown>).role === "assistant" || + (value as Record<string, unknown>).role === "user") && + typeof (value as Record<string, unknown>).content === "string"; + if (!Array.isArray(parsed) || parsed.length === 0 || !parsed.every(isPersistedMessage)) { resetMessagesForSession(); + messageIdCounter = 1; return; } - const valid = parsed.filter( - (m): m is Message => - m !== null && - typeof m === "object" && - typeof (m as Record<string, unknown>).id === "number" && - typeof (m as Record<string, unknown>).role === "string" && - typeof (m as Record<string, unknown>).content === "string" - ); - if (valid.length > 0) { - messages.value = valid; - messageIdCounter = Math.max(...valid.map((m) => m.id)) + 1; - } else { - resetMessagesForSession(); - } + messages.value = parsed; + messageIdCounter = Math.max(...parsed.map((m) => m.id)) + 1;As per coding guidelines "Validate input boundaries, auth/authz implications, and secret management."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@clients/web/apps/chat/src/App.vue` around lines 261 - 285, Reject the entire persisted payload unless every entry is strictly valid: change the current filter-based validation to assert that parsed is a non-empty array and that every element passes a stricter type guard (use resetMessagesForSession, messages.value, messageIdCounter to locate the logic). The guard should verify each item is non-null object, role and content are strings, and id is a finite integer (e.g., Number.isFinite(id) && Number.isInteger(id) and optionally id >= 0) so NaN/Infinity are rejected; if any element fails, call resetMessagesForSession(); otherwise set messages.value = parsed as Message[] and set messageIdCounter = Math.max(...parsed.map(m => m.id)) + 1.clients/web/apps/chat/src/composables/useChat.spec.ts (1)
516-549:⚠️ Potential issue | 🟠 MajorThese tests still race the watcher-driven session-list request.
chat.createSession()flipsisSessionReadyand triggersfetchSessionList()before the explicitawait chat.fetchSessionList(). With only one queued response, one call consumes the mock and the other falls back to[], so the assertions are timing-dependent. Either removecreateSession()here—fetchSessionList()only needs a ready gateway—or enqueue two responses.As per coding guidelines "Look for behavioral regressions, missing tests, and contract breaks across modules."
Also applies to: 555-565
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@clients/web/apps/chat/src/composables/useChat.spec.ts` around lines 516 - 549, The test races because chat.createSession() flips isSessionReady and triggers the watcher-driven fetchSessionList before the explicit await, consuming the single mocked response; fix by ensuring two mocked responses are queued (call fetchMock.mockResolvedValueOnce(...) twice with the same Response before invoking chat.createSession()), or alternatively remove the call to chat.createSession() and rely only on a ready gateway plus the explicit await chat.fetchSessionList(); update the test around use of chat.createSession and chat.fetchSessionList so both potential fetches have mocked responses.openspec/config.yaml (1)
32-36:⚠️ Potential issue | 🟠 MajorRestore explicit Rust verification steps (workspace fmt, clippy, and cargo test).
The verify block is still missing explicit
cargo clippy --all-targets -- -D warningsandcargo test, and Line 35 should use workspace-wide formatting check (cargo fmt --all -- --check) for consistency.Suggested fix
verify: commands: - make rust-test - make web-test-all - - cargo fmt --check + - cargo fmt --all -- --check + - cargo clippy --all-targets -- -D warnings + - cargo test - pnpm checkBased on learnings: "Applies to clients/agent-runtime/**/*.rs : Run
cargo fmt --all -- --check,cargo clippy --all-targets -- -D warnings, andcargo testfor code validation, or document which checks were skipped and why".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@openspec/config.yaml` around lines 32 - 36, The verify commands currently lack explicit workspace Rust checks; replace the single-line `cargo fmt --check` with a workspace format check `cargo fmt --all -- --check`, and add the missing Rust verification commands `cargo clippy --all-targets -- -D warnings` and `cargo test` into the commands list so the verify block runs workspace-wide formatting, clippy (all targets, treat warnings as errors), and tests; update the commands sequence around the existing entries (`make rust-test`, `make web-test-all`, `pnpm check`) to include these three Rust commands.openspec/changes/archive/2026-03-28-session-memory-visibility/tasks.md (1)
143-146:⚠️ Potential issue | 🟠 MajorTask 2.3 still documents a truncated token hash.
Task 1.6 and the gateway code now use the full 64-char SHA-256 hex digest, but this step still says "SHA-256 prefix". Leaving that wording here will reintroduce weaker session scoping in follow-up work.
💡 Suggested fix
- - Compute `token_hash` (SHA-256 prefix, same as task 1.6) + - Compute `token_hash` using the full SHA-256 hex digest (64 chars), same as task 1.6As per coding guidelines "Verify technical accuracy and that docs stay aligned with code changes."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@openspec/changes/archive/2026-03-28-session-memory-visibility/tasks.md` around lines 143 - 146, Update the Task 2.3 doc for the handle_session_list handler to match current implementation: replace the phrase "Compute `token_hash` (SHA-256 prefix, same as task 1.6)" with instructions to compute the full SHA-256 hex digest (64 hex characters) for `token_hash` and then call memory.list_sessions_for_token(token_hash, limit, offset); ensure references to `handle_session_list`, `token_hash`, and `memory.list_sessions_for_token` are accurate and consistent with Task 1.6 and the gateway code.clients/web/apps/chat/src/composables/useChat.ts (1)
535-546:⚠️ Potential issue | 🟠 MajorClear session history when the gateway/auth context changes.
fetchSessionList()now resets on fetch failures, but the old list still survives logout, credential invalidation, and base-URL switches because neither auth-context watcher clearssessionList. That can keep rendering the previous token's sessions until another successful fetch happens.💡 Suggested fix
watch( () => gateway.baseUrl.value, () => { + sessionList.value = []; currentSessionId.value = ""; updateSessionState( gateway.isGatewayReady.value ? createSessionState("session_pending", null, false, canResumeSession.value) : createSessionState("idle") ); } ); watch( () => gateway.isGatewayReady.value, (ready) => { if (!ready) { + sessionList.value = []; currentSessionId.value = ""; updateSessionState(createSessionState("idle")); return; }As per coding guidelines "Security first, performance second. Validate input boundaries, auth/authz implications, and secret management."
Also applies to: 571-579
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@clients/web/apps/chat/src/composables/useChat.ts` around lines 535 - 546, fetchSessionList currently clears sessionList on fetch failure but doesn't proactively clear it when gateway/auth context changes (so old sessions remain visible across logout/credential/base-URL changes); update the logic by clearing sessionList.value whenever the gateway/auth context transitions or becomes not ready—e.g., add watchers/listeners for gateway.isGatewayReady (when it turns false), auth/credentials change, and base-URL switches and ensure they call sessionList.value = [] (also apply the same defensive clearing behavior referenced around lines 571-579); reference the fetchSessionList function and the sessionList reactive to locate where to attach these watchers and to keep behavior consistent.clients/web/apps/dashboard/src/components/memory/MemoryList.vue (1)
42-49:⚠️ Potential issue | 🟠 MajorMake the delete dialog fully accessible.
The
alertdialogstill has no accessible name/description, focus can escape to the page behind it, and focus is never restored on close. That leaves keyboard and screen-reader users without a reliable modal interaction for a destructive action.As per coding guidelines "Ensure accessibility (A11y) and proper use of Tailwind CSS classes."
Also applies to: 142-157
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@clients/web/apps/dashboard/src/components/memory/MemoryList.vue` around lines 42 - 49, The delete modal triggered by requestDelete does not implement proper accessible modal behavior: when opening (requestDelete) save the currently focused element, set the alertdialog element attributes aria-modal="true" and an accessible name/description via aria-labelledby/aria-describedby (add IDs to the dialog title and message), move focus to the confirm button (confirmBtnRef) and activate a focus trap so keyboard focus cannot escape the dialog, and when closing (cancelDelete / successful delete) restore focus to the previously saved element and remove the focus trap; also mark background content inert or aria-hidden while the dialog is open so screen readers cannot access page content behind the modal. Include these changes in the code paths around requestDelete, cancelDelete, confirmingDelete and confirmBtnRef.openspec/changes/archive/2026-03-28-session-memory-visibility/specs/sessions/spec.md (1)
390-406:⚠️ Potential issue | 🟡 MinorScenario method names should match SESS-9 contract.
Same issue as the main spec: scenarios reference
start_session()but SESS-9 definesupsert_session(). Align scenario method names with the trait signatures.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@openspec/changes/archive/2026-03-28-session-memory-visibility/specs/sessions/spec.md` around lines 390 - 406, The scenario method names use start_session() but the SESS-9 contract defines upsert_session(); update both scenarios to call upsert_session("...") instead of start_session("...") so the Gherkin matches the trait signature (ensure references to the sessions table and expected return values remain the same for the sqlite scenario and that the markdown backend still expects an Ok default Session).openspec/specs/sessions/spec.md (1)
391-407:⚠️ Potential issue | 🟡 MinorScenario method names inconsistent with SESS-9 method signatures.
The scenarios at lines 395 and 404 reference
start_session("abc-123")but SESS-9 (line 381) definesupsert_session(session_id: &str, token_hash: Option<&str>). Update scenarios to useupsert_sessionfor consistency with the trait contract.Given the memory backend is "markdown" -When start_session("abc-123") is called +When upsert_session("abc-123", None) is called Then the method MUST return Ok with a default Session valueGiven the memory backend is "sqlite" -When start_session("new-session") is called +When upsert_session("new-session", None) is called Then a session record MUST be inserted into the sessions table🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@openspec/specs/sessions/spec.md` around lines 391 - 407, Update the two Gherkin scenarios to call the trait method name and signature defined in SESS-9: replace start_session("...") with upsert_session(session_id: &str, token_hash: Option<&str>) using an explicit token_hash of None (e.g., upsert_session("abc-123", None) and upsert_session("new-session", None)); ensure the expected outcomes remain the same (Ok with default Session for non-SQLite backend, and insertion/returned Session id "new-session" with ended_at None for sqlite).clients/agent-runtime/src/memory/sqlite.rs (1)
846-899:⚠️ Potential issue | 🔴 CriticalScope session mutations by
token_hash.
upsert_session(),end_session(), andupdate_session_activity()all mutate byidalone even though the row carriestoken_hash. A caller that can present another token'ssession_idcan reopen, close, or bump that session without proving ownership. Please threadtoken_hashthrough the mutating APIs and make the SQL rejectid/token_hashmismatches.As per coding guidelines "Security first, performance second."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@clients/agent-runtime/src/memory/sqlite.rs` around lines 846 - 899, The three mutators upsert_session, end_session, and update_session_activity must require and check token ownership: add a token_hash parameter to end_session and update_session_activity signatures (thread token_hash through the spawn_blocking closure), include token_hash in the INSERT values for upsert_session (already accepted) and change the SQL so that mutations only affect rows matching both id AND token_hash (e.g., WHERE id = ? AND token_hash = ?), and adjust the ON CONFLICT DO UPDATE in upsert_session to keep/update token_hash via excluded.token_hash or ensure it does not allow changing token_hash unintentionally; update all params![...] argument lists to pass the token_hash in the correct position for each statement.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@clients/agent-runtime/src/gateway/admin.rs`:
- Around line 2102-2105: The current handler uses
serde_json::to_value(response).unwrap_or_default() which hides serialization
errors by returning an empty JSON with StatusCode::OK; change this to surface
failures by handling the Result from serde_json::to_value explicitly (e.g.,
map_err/log the serde_json::Error and return an appropriate error response
instead of OK) so that the Json(...) and StatusCode::OK branch only runs on
successful serialization; update the call sites using serde_json::to_value,
remove unwrap_or_default, and return or log the serialization error (using the
surrounding handler's error type) so failures aren’t silently masked.
- Around line 2233-2241: The category matching is case-sensitive and can
misclassify values like "Core"/"CORE" as Custom; normalize the incoming
params.category before matching (e.g., map .to_ascii_lowercase() or
.to_lowercase()) and then match the normalized string against "core", "daily",
and "conversation" to produce MemoryCategory::Core, ::Daily, or ::Conversation
(use the same params.category.as_deref().map(...) site and the MemoryCategory
enum to locate the code), so values produced by MemoryCategory::to_string() or
any case-variant from clients are handled correctly.
In `@clients/agent-runtime/src/gateway/mod.rs`:
- Around line 1739-1742: The code calls
state.mem.update_session_activity(&session_id).await unconditionally, which
increments session counters even when persist_idempotency is false for retryable
webhook outcomes; wrap the update_session_activity call in a guard that checks
the idempotency persistence flag (the same condition used when calling
upsert_session()/persisting idempotency) so it only runs when
persist_idempotency is true, and apply this same guard to the early-return and
legacy branches that currently call update_session_activity (i.e., mirror the
condition around update_session_activity in the existing branch where
upsert_session() is used).
In `@clients/agent-runtime/src/gateway/sessions.rs`:
- Around line 46-53: The current check in the session list handler lets any
non-empty bearer token become an authorized scope when pairing is disabled;
change the logic so access is denied by default unless pairing is active or an
explicit opt-in config is set. Modify the branch around
state.pairing.require_pairing() and state.pairing.is_authenticated(&token) to
return UNAUTHORIZED when pairing is disabled, and only permit the previous
"unpaired token-as-scope" behavior if a new explicit configuration flag (e.g.,
allow_unpaired_session_scopes) on the runtime config/state is true; update the
authorization flow and any docs/comments to use that flag and keep calls to
is_authenticated(&token) unchanged for the paired case. Ensure the new config
flag is read from state (e.g., state.config.allow_unpaired_session_scopes) so
the default remains deny-by-default.
In `@clients/agent-runtime/src/memory/sqlite.rs`:
- Around line 921-923: The pagination query builds sql_query selecting sessions
ordered by {sort_col} and {order_dir}; to make paging deterministic add the
primary key as a secondary sort key in the same direction (e.g., ORDER BY
{sort_col} {order_dir}, id {order_dir}) so rows with identical timestamps never
move between pages—update the sql_query format string at the sessions SELECT
(the one using "ORDER BY {sort_col} {order_dir} LIMIT ?1 OFFSET ?2") and the
analogous query around the 1037-1041 region to append ", id {order_dir}" to the
ORDER BY clause.
- Around line 906-913: list_sessions currently accepts an unbounded u32 limit
and may allocate large vectors; fix by capping the requested limit in the
storage layer before querying/collecting: introduce a MAX_LIST_LIMIT constant
(e.g. 1000), compute let capped = (limit as usize).min(MAX_LIST_LIMIT) (or use
u32::min and convert as needed), use capped for any SQL LIMIT/offset and for
sizing/collecting the Vec<SessionEntry> (or call .take(capped) on the stream),
and return results based on capped instead of the original limit; apply the same
clamp pattern to the other list API in this module (the second list_* function)
to prevent oversized scans/allocations.
In `@clients/agent-runtime/src/memory/traits.rs`:
- Around line 56-65: The FromStr impl for SessionStatus currently uses type Err
= String which is inconsistent with the crate's use of anyhow for errors; update
the impl std::str::FromStr for SessionStatus (the from_str method) to return
Result<Self, anyhow::Error> (or a crate-specific error type) and convert the
unknown-case error into an anyhow::Error (e.g., with anyhow::anyhow! or
Into<anyhow::Error>) so callers get a consistent error type across the codebase.
In `@clients/web/apps/chat/src/App.vue`:
- Around line 125-130: The extra explicit refresh in handleSidebarNewChat is
redundant and causes duplicate session-list requests: remove the call to
chat.fetchSessionList() from inside handleSidebarNewChat so that
beginSession(false) can transition useChat to session_ready and the existing
watcher in useChat.ts will trigger a single fetchSessionList() call; locate
function handleSidebarNewChat and delete the chat.fetchSessionList() line
(leaving persistMessages(), chat.clearSession(), and await beginSession(false)
intact).
In `@clients/web/apps/chat/src/components/SessionSidebar.spec.ts`:
- Around line 17-35: The test fixtures in sampleSessions do not include the
required ended_at field expected by the SessionListItem contract; update each
object in the sampleSessions array to include an ended_at timestamp (e.g., ISO
string) consistent with started_at/last_activity values so the fixtures match
the SessionListItem shape used by SessionSidebar.spec.ts and any helpers that
validate session objects.
In `@clients/web/apps/chat/src/composables/useGateway.ts`:
- Around line 473-480: The request is using page/per_page but the backend
expects limit and offset, so replace the query string passed to gatewayUrl in
useGateway.ts: send limit=${clampedLimit}&offset=${clampedOffset} (use the
existing clampedLimit and clampedOffset variables) instead of page and per_page
so SessionListParams receives the correct values; update the fetch call that
builds the URL (the line using gatewayUrl(`/session/list?...`) and related
variables page, clampedLimit, clampedOffset) accordingly.
In `@clients/web/apps/dashboard/src/components/memory/MemoryList.spec.ts`:
- Around line 71-169: Add tests to cover prop-driven refetching: after mounting
MemoryList with mountMemoryList and mocking initial response via
mockMemoryResponse, change props/session filters (sessionIdFilter,
categoryFilter, searchFilter) either by calling wrapper.setProps or updating the
parent App-like wrapper, call await flushPromises(), and assert that fetchMock
was called again with the updated query params and that the rendered rows/text
update accordingly; reference MemoryList, mountMemoryList, mockMemoryResponse,
and the three filter props when adding these assertions to ensure watchers
trigger a new fetch and UI updates.
In `@clients/web/apps/dashboard/src/components/memory/MemoryStats.spec.ts`:
- Around line 48-129: Tests use vi.dynamicImportSettled() which only waits for
dynamic imports, causing flaky assertions that run before the fetch in
onMounted() populates memoryStats; replace each vi.dynamicImportSettled() call
with flushPromises() (imported from `@vue/test-utils`) in MemoryStats.spec.ts so
the test waits for pending promises (the fetch) to resolve after calling
mountStats() before making assertions.
In `@clients/web/apps/dashboard/src/components/sessions/SessionDetail.vue`:
- Around line 20-24: The load() watcher can finish out-of-order and overwrite
newer session data; to fix, implement request sequencing or aborting: add a
component-scoped AbortController (or numeric requestId ref) used by load() and
passed to admin.fetchSessionDetail (or used to validate responses) so that when
props.sessionId changes you abort the previous controller or increment the
requestId, then only apply results if the signal is not aborted or the response
matches the latest requestId; update useAdmin.fetchSessionDetail to accept an
AbortSignal (or make load() ignore stale responses by checking requestId) and
ensure watch(() => props.sessionId, load, { immediate: true }) uses this
mechanism to prevent older fetches from overwriting newer data.
In `@clients/web/apps/dashboard/src/components/sessions/SessionFilters.vue`:
- Around line 11-21: onStatusChange and onSortChange currently emit raw/CASTed
select values which can be tampered at runtime; clamp the outputs to the
supported enums before emitting by validating status.value against the allowed
set ("all", "active", "ended") and mapping non-matching inputs to undefined (or
a safe default), and validate sort.value against the allowed union
("last_activity", "started_at") before calling emit("update:sort", ...); update
the functions onStatusChange and onSortChange to perform these checks and only
emit sanitized enum values via emit.
In `@clients/web/apps/dashboard/src/components/sessions/SessionList.vue`:
- Around line 18-29: The component hardcodes perPage = ref(25) but never exposes
it to the UI, which prevents changing page size; either surface it as a reactive
control or remove the expectation of a page-size-selector. Fix by wiring a
visible page-size selector bound to perPage (e.g., add a <PageSizeSelector
v-model="perPage" /> or similar) so changing the control updates perPage.value
before calling load(), and ensure load() (which constructs params and calls
admin.fetchSessions) uses perPage.value as it already does; alternatively, if
you don't want adjustable page size, remove the unused UI requirement and any
references to a page-size-selector and keep perPage as a constant used by
load()/fetchSessions.
In `@clients/web/apps/dashboard/src/composables/useAdmin.ts`:
- Line 104: The response is wrapped as { session, memory_summary } but the code
casts the whole payload to AdminSessionDetail; change the fetch handling to
unpack the wrapper: await res.json() into a const resp, then set
sessionDetail.value = resp.session as AdminSessionDetail and also store
resp.memory_summary (either set sessionDetail.value.memory_summary =
resp.memory_summary or assign it to the appropriate memorySummary ref/variable).
Update the code around sessionDetail to use resp.session and ensure the
memory_summary field is preserved.
In `@clients/web/apps/dashboard/src/types/admin-sessions.ts`:
- Around line 10-13: The TypeScript type for AdminSessionDetail.metadata is too
narrow (Record<string, unknown> | null) compared to the backend
Option<serde_json::Value>; update the metadata declaration on the
AdminSessionDetail interface to accept any JSON value (e.g., metadata?: unknown
| null) so arrays and scalar JSON values are represented, and adjust any
consumers of AdminSessionDetail.metadata to stop using unsafe casts tied to
object-only shapes; reference: AdminSessionDetail, metadata, memory_summary,
AdminSessionView.
In `@openspec/changes/archive/2026-03-28-session-memory-visibility/design.md`:
- Around line 65-76: The doc's API contract is out of sync with the shipped
implementation: replace mixed pagination and session-scoping descriptions with
the actual contract—use limit/offset instead of page/per_page, describe
bearer-token-hash scoping for /session/list (not session_id header history), and
change the admin session detail /web/admin/sessions/:id to reference
memory_summary and a separate memory view (/web/admin/memory) rather than
embedding memory entries inline; also ensure endpoints /web/admin/sessions,
/web/admin/memory, /web/admin/memory/stats, and DELETE /web/admin/memory/:key
reflect the real params, filters, and pagination semantics.
In
`@openspec/changes/archive/2026-03-28-session-memory-visibility/exploration.md`:
- Around line 76-83: Update the endpoint inventory to use the implemented
end-user path: replace the incorrect `GET /session/history` entry with `GET
/session/list` so the list of routes matches the code and docs; ensure the `GET
/session/list` string appears exactly once in the block that currently contains
`GET /web/admin/sessions`, `GET /web/admin/sessions/:id`, `GET
/web/admin/memory`, `GET /web/admin/memory/stats`, `GET /session/history`, and
`DELETE /web/admin/memory/:key`.
- Line 170: In the exploration.md table row that currently lists
`admin-config.ts` as the type for session/memory API, update that reference to
`admin-sessions.ts`; specifically edit the table entry text (the cell that reads
"clients/.../admin-config.ts") so it points to `admin-sessions.ts` and confirm
the accompanying description ("New types for session and memory views") still
applies to the renamed type.
In
`@openspec/changes/archive/2026-03-28-session-memory-visibility/specs/client-surfaces/spec.md`:
- Around line 324-340: The AdminSessionView type in admin-config.ts is missing
the status field used by the dashboard; update the AdminSessionView type
definition to include a status property (matching the API's enum/string shape
used by the UI, e.g., "active" | "ended" or string) so
PaginatedResponse<AdminSessionView> matches the actual GET /web/admin/sessions
response and the dashboard's session.status usage; ensure any dependent types or
tests referencing AdminSessionView are updated accordingly to reflect the new
status field.
In `@openspec/specs/client-surfaces/spec.md`:
- Line 380: The spec's session-list route is out of sync with the web client:
change CS-5/CS-6 (and the related section around lines 408-416) to reference the
actual endpoint used by the client (GET
/api/v1/sessions?limit=<...>&offset=<...>) or explicitly state that GET
/session/list is an accepted alias for that endpoint; update the route text and
any examples to match the implementation in
clients/web/apps/chat/src/composables/useGateway.ts so the contract is accurate.
In `@openspec/specs/memory-visibility/spec.md`:
- Around line 25-34: The spec promises exact pagination totals for full-text
searches but the implementation uses mem.recall(q, 200, ...) (see mem.recall
call in admin gateway) which caps search results at 200 and can make total
counts undercount; update the spec text in memory-visibility/spec.md (the
sections around the listed query params and the other mentioned ranges) to
explicitly document that `q`-based searches are capped at 200 results and that
`total` reflects the capped result set (or state that totals are best-effort and
may be truncated), or alternatively implement a separate counting path in the
admin gateway to return an exact total (i.e., call the counting API
before/alongside mem.recall) — choose and apply one of these fixes and make the
spec match the chosen behavior.
---
Duplicate comments:
In `@clients/agent-runtime/src/memory/sqlite.rs`:
- Around line 846-899: The three mutators upsert_session, end_session, and
update_session_activity must require and check token ownership: add a token_hash
parameter to end_session and update_session_activity signatures (thread
token_hash through the spawn_blocking closure), include token_hash in the INSERT
values for upsert_session (already accepted) and change the SQL so that
mutations only affect rows matching both id AND token_hash (e.g., WHERE id = ?
AND token_hash = ?), and adjust the ON CONFLICT DO UPDATE in upsert_session to
keep/update token_hash via excluded.token_hash or ensure it does not allow
changing token_hash unintentionally; update all params![...] argument lists to
pass the token_hash in the correct position for each statement.
In `@clients/web/apps/chat/src/App.vue`:
- Around line 261-285: Reject the entire persisted payload unless every entry is
strictly valid: change the current filter-based validation to assert that parsed
is a non-empty array and that every element passes a stricter type guard (use
resetMessagesForSession, messages.value, messageIdCounter to locate the logic).
The guard should verify each item is non-null object, role and content are
strings, and id is a finite integer (e.g., Number.isFinite(id) &&
Number.isInteger(id) and optionally id >= 0) so NaN/Infinity are rejected; if
any element fails, call resetMessagesForSession(); otherwise set messages.value
= parsed as Message[] and set messageIdCounter = Math.max(...parsed.map(m =>
m.id)) + 1.
In `@clients/web/apps/chat/src/composables/useChat.spec.ts`:
- Around line 516-549: The test races because chat.createSession() flips
isSessionReady and triggers the watcher-driven fetchSessionList before the
explicit await, consuming the single mocked response; fix by ensuring two mocked
responses are queued (call fetchMock.mockResolvedValueOnce(...) twice with the
same Response before invoking chat.createSession()), or alternatively remove the
call to chat.createSession() and rely only on a ready gateway plus the explicit
await chat.fetchSessionList(); update the test around use of chat.createSession
and chat.fetchSessionList so both potential fetches have mocked responses.
In `@clients/web/apps/chat/src/composables/useChat.ts`:
- Around line 535-546: fetchSessionList currently clears sessionList on fetch
failure but doesn't proactively clear it when gateway/auth context changes (so
old sessions remain visible across logout/credential/base-URL changes); update
the logic by clearing sessionList.value whenever the gateway/auth context
transitions or becomes not ready—e.g., add watchers/listeners for
gateway.isGatewayReady (when it turns false), auth/credentials change, and
base-URL switches and ensure they call sessionList.value = [] (also apply the
same defensive clearing behavior referenced around lines 571-579); reference the
fetchSessionList function and the sessionList reactive to locate where to attach
these watchers and to keep behavior consistent.
In `@clients/web/apps/chat/src/composables/useGateway.spec.ts`:
- Around line 495-499: The test (in useGateway.spec.ts) is asserting query
params "page" and "per_page" but the backend expects "limit" and "offset"
(SessionListParams), so update the frontend URL construction or the test
accordingly: locate where the sessions list URL is built (the code under test
that triggers fetch — look for the function that calls fetch and names like
"/session/list" or the test's subject) and change the query parameter names from
page/per_page to limit/offset (or convert page/per_page to computed limit and
offset) so the request matches SessionListParams; then update the spec
expectations to assert "limit" and "offset" (or the computed numeric values) and
keep the Authorization header assertion as-is.
In `@clients/web/apps/dashboard/src/components/memory/MemoryList.vue`:
- Around line 42-49: The delete modal triggered by requestDelete does not
implement proper accessible modal behavior: when opening (requestDelete) save
the currently focused element, set the alertdialog element attributes
aria-modal="true" and an accessible name/description via
aria-labelledby/aria-describedby (add IDs to the dialog title and message), move
focus to the confirm button (confirmBtnRef) and activate a focus trap so
keyboard focus cannot escape the dialog, and when closing (cancelDelete /
successful delete) restore focus to the previously saved element and remove the
focus trap; also mark background content inert or aria-hidden while the dialog
is open so screen readers cannot access page content behind the modal. Include
these changes in the code paths around requestDelete, cancelDelete,
confirmingDelete and confirmBtnRef.
In
`@openspec/changes/archive/2026-03-28-session-memory-visibility/specs/sessions/spec.md`:
- Around line 390-406: The scenario method names use start_session() but the
SESS-9 contract defines upsert_session(); update both scenarios to call
upsert_session("...") instead of start_session("...") so the Gherkin matches the
trait signature (ensure references to the sessions table and expected return
values remain the same for the sqlite scenario and that the markdown backend
still expects an Ok default Session).
In `@openspec/changes/archive/2026-03-28-session-memory-visibility/tasks.md`:
- Around line 143-146: Update the Task 2.3 doc for the handle_session_list
handler to match current implementation: replace the phrase "Compute
`token_hash` (SHA-256 prefix, same as task 1.6)" with instructions to compute
the full SHA-256 hex digest (64 hex characters) for `token_hash` and then call
memory.list_sessions_for_token(token_hash, limit, offset); ensure references to
`handle_session_list`, `token_hash`, and `memory.list_sessions_for_token` are
accurate and consistent with Task 1.6 and the gateway code.
In `@openspec/config.yaml`:
- Around line 32-36: The verify commands currently lack explicit workspace Rust
checks; replace the single-line `cargo fmt --check` with a workspace format
check `cargo fmt --all -- --check`, and add the missing Rust verification
commands `cargo clippy --all-targets -- -D warnings` and `cargo test` into the
commands list so the verify block runs workspace-wide formatting, clippy (all
targets, treat warnings as errors), and tests; update the commands sequence
around the existing entries (`make rust-test`, `make web-test-all`, `pnpm
check`) to include these three Rust commands.
In `@openspec/specs/sessions/spec.md`:
- Around line 391-407: Update the two Gherkin scenarios to call the trait method
name and signature defined in SESS-9: replace start_session("...") with
upsert_session(session_id: &str, token_hash: Option<&str>) using an explicit
token_hash of None (e.g., upsert_session("abc-123", None) and
upsert_session("new-session", None)); ensure the expected outcomes remain the
same (Ok with default Session for non-SQLite backend, and insertion/returned
Session id "new-session" with ended_at None for sqlite).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: f15efa00-dfdf-460e-b188-117233066676
📒 Files selected for processing (43)
clients/agent-runtime/src/gateway/admin.rsclients/agent-runtime/src/gateway/mod.rsclients/agent-runtime/src/gateway/sessions.rsclients/agent-runtime/src/memory/hygiene.rsclients/agent-runtime/src/memory/mod.rsclients/agent-runtime/src/memory/sqlite.rsclients/agent-runtime/src/memory/traits.rsclients/web/apps/chat/src/App.spec.tsclients/web/apps/chat/src/App.vueclients/web/apps/chat/src/components/SessionSidebar.spec.tsclients/web/apps/chat/src/components/SessionSidebar.vueclients/web/apps/chat/src/composables/useChat.spec.tsclients/web/apps/chat/src/composables/useChat.tsclients/web/apps/chat/src/composables/useGateway.spec.tsclients/web/apps/chat/src/composables/useGateway.tsclients/web/apps/chat/src/types/chat.tsclients/web/apps/dashboard/src/App.vueclients/web/apps/dashboard/src/components/memory/MemoryFilters.vueclients/web/apps/dashboard/src/components/memory/MemoryList.spec.tsclients/web/apps/dashboard/src/components/memory/MemoryList.vueclients/web/apps/dashboard/src/components/memory/MemoryStats.spec.tsclients/web/apps/dashboard/src/components/memory/MemoryStats.vueclients/web/apps/dashboard/src/components/sessions/SessionDetail.spec.tsclients/web/apps/dashboard/src/components/sessions/SessionDetail.vueclients/web/apps/dashboard/src/components/sessions/SessionFilters.vueclients/web/apps/dashboard/src/components/sessions/SessionList.spec.tsclients/web/apps/dashboard/src/components/sessions/SessionList.vueclients/web/apps/dashboard/src/composables/useAdmin.spec.tsclients/web/apps/dashboard/src/composables/useAdmin.tsclients/web/apps/dashboard/src/types/admin-sessions.tsopenspec/changes/archive/2026-03-28-session-memory-visibility/design.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/exploration.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/proposal.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/specs/client-surfaces/spec.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/specs/memory-visibility/spec.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/specs/sessions/spec.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/state.yamlopenspec/changes/archive/2026-03-28-session-memory-visibility/tasks.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/verify-report.mdopenspec/config.yamlopenspec/specs/client-surfaces/spec.mdopenspec/specs/memory-visibility/spec.mdopenspec/specs/sessions/spec.md
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: pr-checks
- GitHub Check: sonar
- GitHub Check: Cloudflare Pages
🧰 Additional context used
📓 Path-based instructions (8)
**/*
⚙️ CodeRabbit configuration file
**/*: Security first, performance second.
Validate input boundaries, auth/authz implications, and secret management.
Look for behavioral regressions, missing tests, and contract breaks across modules.
Files:
openspec/changes/archive/2026-03-28-session-memory-visibility/state.yamlclients/agent-runtime/src/memory/mod.rsclients/web/apps/dashboard/src/components/sessions/SessionList.spec.tsopenspec/changes/archive/2026-03-28-session-memory-visibility/verify-report.mdclients/web/apps/dashboard/src/components/sessions/SessionDetail.spec.tsclients/web/apps/chat/src/App.spec.tsclients/agent-runtime/src/memory/hygiene.rsclients/web/apps/dashboard/src/components/memory/MemoryList.spec.tsclients/web/apps/chat/src/composables/useGateway.tsclients/web/apps/dashboard/src/components/sessions/SessionFilters.vueopenspec/changes/archive/2026-03-28-session-memory-visibility/exploration.mdclients/web/apps/dashboard/src/components/memory/MemoryStats.vueclients/web/apps/chat/src/components/SessionSidebar.vueclients/web/apps/dashboard/src/components/memory/MemoryFilters.vueclients/web/apps/chat/src/composables/useGateway.spec.tsclients/web/apps/chat/src/components/SessionSidebar.spec.tsclients/web/apps/chat/src/types/chat.tsclients/web/apps/dashboard/src/components/memory/MemoryStats.spec.tsclients/web/apps/dashboard/src/composables/useAdmin.spec.tsopenspec/changes/archive/2026-03-28-session-memory-visibility/design.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/tasks.mdclients/web/apps/chat/src/composables/useChat.spec.tsopenspec/changes/archive/2026-03-28-session-memory-visibility/proposal.mdclients/web/apps/dashboard/src/components/memory/MemoryList.vueclients/web/apps/chat/src/composables/useChat.tsopenspec/changes/archive/2026-03-28-session-memory-visibility/specs/memory-visibility/spec.mdclients/agent-runtime/src/gateway/sessions.rsclients/web/apps/dashboard/src/components/sessions/SessionDetail.vueopenspec/config.yamlclients/web/apps/dashboard/src/components/sessions/SessionList.vueopenspec/changes/archive/2026-03-28-session-memory-visibility/specs/client-surfaces/spec.mdclients/web/apps/dashboard/src/types/admin-sessions.tsopenspec/specs/sessions/spec.mdclients/web/apps/dashboard/src/App.vueopenspec/specs/client-surfaces/spec.mdopenspec/specs/memory-visibility/spec.mdclients/agent-runtime/src/gateway/mod.rsopenspec/changes/archive/2026-03-28-session-memory-visibility/specs/sessions/spec.mdclients/web/apps/dashboard/src/composables/useAdmin.tsclients/agent-runtime/src/gateway/admin.rsclients/agent-runtime/src/memory/traits.rsclients/agent-runtime/src/memory/sqlite.rsclients/web/apps/chat/src/App.vue
clients/agent-runtime/src/**/*.rs
📄 CodeRabbit inference engine (clients/agent-runtime/AGENTS.md)
clients/agent-runtime/src/**/*.rs: Never log secrets, tokens, raw credentials, or sensitive payloads in any logging statements
Avoid unnecessary allocations, clones, and blocking operations to maintain performance and efficiency
Files:
clients/agent-runtime/src/memory/mod.rsclients/agent-runtime/src/memory/hygiene.rsclients/agent-runtime/src/gateway/sessions.rsclients/agent-runtime/src/gateway/mod.rsclients/agent-runtime/src/gateway/admin.rsclients/agent-runtime/src/memory/traits.rsclients/agent-runtime/src/memory/sqlite.rs
clients/agent-runtime/**/*.rs
📄 CodeRabbit inference engine (clients/agent-runtime/AGENTS.md)
Run
cargo fmt --all -- --check,cargo clippy --all-targets -- -D warnings, andcargo testfor code validation, or document which checks were skipped and why
Files:
clients/agent-runtime/src/memory/mod.rsclients/agent-runtime/src/memory/hygiene.rsclients/agent-runtime/src/gateway/sessions.rsclients/agent-runtime/src/gateway/mod.rsclients/agent-runtime/src/gateway/admin.rsclients/agent-runtime/src/memory/traits.rsclients/agent-runtime/src/memory/sqlite.rs
**/*.rs
⚙️ CodeRabbit configuration file
**/*.rs: Focus on Rust idioms, memory safety, and ownership/borrowing correctness.
Flag unnecessary clones, unchecked panics in production paths, and weak error context.
Prioritize unsafe blocks, FFI boundaries, concurrency races, and secret handling.
Files:
clients/agent-runtime/src/memory/mod.rsclients/agent-runtime/src/memory/hygiene.rsclients/agent-runtime/src/gateway/sessions.rsclients/agent-runtime/src/gateway/mod.rsclients/agent-runtime/src/gateway/admin.rsclients/agent-runtime/src/memory/traits.rsclients/agent-runtime/src/memory/sqlite.rs
**/*.{md,mdx}
⚙️ CodeRabbit configuration file
**/*.{md,mdx}: Verify technical accuracy and that docs stay aligned with code changes.
For user-facing docs, check EN/ES parity or explicitly note pending translation gaps.
Files:
openspec/changes/archive/2026-03-28-session-memory-visibility/verify-report.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/exploration.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/design.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/tasks.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/proposal.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/specs/memory-visibility/spec.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/specs/client-surfaces/spec.mdopenspec/specs/sessions/spec.mdopenspec/specs/client-surfaces/spec.mdopenspec/specs/memory-visibility/spec.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/specs/sessions/spec.md
**/*.vue
⚙️ CodeRabbit configuration file
**/*.vue: Enforce Vue 3 Composition API with <script setup>.
Ensure accessibility (A11y) and proper use of Tailwind CSS classes.
Check for proper prop validation and emitted events documentation.
Files:
clients/web/apps/dashboard/src/components/sessions/SessionFilters.vueclients/web/apps/dashboard/src/components/memory/MemoryStats.vueclients/web/apps/chat/src/components/SessionSidebar.vueclients/web/apps/dashboard/src/components/memory/MemoryFilters.vueclients/web/apps/dashboard/src/components/memory/MemoryList.vueclients/web/apps/dashboard/src/components/sessions/SessionDetail.vueclients/web/apps/dashboard/src/components/sessions/SessionList.vueclients/web/apps/dashboard/src/App.vueclients/web/apps/chat/src/App.vue
clients/agent-runtime/src/{security,gateway,tools}/**/*.rs
📄 CodeRabbit inference engine (clients/agent-runtime/AGENTS.md)
Treat
src/security/,src/gateway/,src/tools/as high-risk surfaces and never broaden filesystem/network execution scope without explicit policy checks
Files:
clients/agent-runtime/src/gateway/sessions.rsclients/agent-runtime/src/gateway/mod.rsclients/agent-runtime/src/gateway/admin.rs
clients/agent-runtime/src/{security,gateway,tools,config}/**/*.rs
📄 CodeRabbit inference engine (clients/agent-runtime/AGENTS.md)
Do not silently weaken security policy or access constraints; keep default behavior secure-by-default with deny-by-default where applicable
Files:
clients/agent-runtime/src/gateway/sessions.rsclients/agent-runtime/src/gateway/mod.rsclients/agent-runtime/src/gateway/admin.rs
🧠 Learnings (13)
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/**/*.rs : Avoid unnecessary allocations, clones, and blocking operations to maintain performance and efficiency
Applied to files:
clients/agent-runtime/src/memory/mod.rsclients/agent-runtime/src/gateway/admin.rsclients/agent-runtime/src/memory/traits.rs
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/**/*.rs : Run `cargo fmt --all -- --check`, `cargo clippy --all-targets -- -D warnings`, and `cargo test` for code validation, or document which checks were skipped and why
Applied to files:
openspec/changes/archive/2026-03-28-session-memory-visibility/verify-report.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/tasks.mdopenspec/config.yamlclients/agent-runtime/src/memory/traits.rs
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/main.rs : Preserve CLI contract unless change is intentional and documented; prefer explicit errors over silent fallback for unsupported critical paths
Applied to files:
openspec/changes/archive/2026-03-28-session-memory-visibility/verify-report.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/tasks.mdclients/agent-runtime/src/gateway/mod.rsopenspec/changes/archive/2026-03-28-session-memory-visibility/specs/sessions/spec.mdclients/agent-runtime/src/gateway/admin.rsclients/agent-runtime/src/memory/traits.rsclients/agent-runtime/src/memory/sqlite.rs
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/tools/**/*.rs : Implement `Tool` trait in `src/tools/` with strict parameter schema, validate and sanitize all inputs, and return structured `ToolResult` without panics in runtime path
Applied to files:
openspec/changes/archive/2026-03-28-session-memory-visibility/tasks.md
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/**/Cargo.toml : Do not add heavy dependencies for minor convenience; justify new crate additions
Applied to files:
openspec/changes/archive/2026-03-28-session-memory-visibility/tasks.md
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/**/Cargo.toml : Preserve release-size profile assumptions in `Cargo.toml` and avoid adding heavy dependencies unless clearly justified
Applied to files:
openspec/changes/archive/2026-03-28-session-memory-visibility/tasks.md
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/{security,gateway,tools,config}/**/*.rs : Do not silently weaken security policy or access constraints; keep default behavior secure-by-default with deny-by-default where applicable
Applied to files:
openspec/changes/archive/2026-03-28-session-memory-visibility/tasks.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/specs/memory-visibility/spec.mdopenspec/config.yamlopenspec/specs/sessions/spec.mdclients/agent-runtime/src/gateway/mod.rsopenspec/changes/archive/2026-03-28-session-memory-visibility/specs/sessions/spec.mdclients/agent-runtime/src/gateway/admin.rsclients/agent-runtime/src/memory/traits.rsclients/agent-runtime/src/memory/sqlite.rs
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/{security,gateway,tools}/**/*.rs : Treat `src/security/`, `src/gateway/`, `src/tools/` as high-risk surfaces and never broaden filesystem/network execution scope without explicit policy checks
Applied to files:
openspec/changes/archive/2026-03-28-session-memory-visibility/tasks.mdopenspec/config.yamlclients/agent-runtime/src/gateway/mod.rsclients/agent-runtime/src/gateway/admin.rsclients/agent-runtime/src/memory/sqlite.rs
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Include threat/risk notes and rollback strategy for security, runtime, and gateway changes; add or update tests for boundary checks and failure modes
Applied to files:
openspec/config.yaml
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Inspect existing module and adjacent tests before editing; define scope boundary with one concern per PR and avoid mixed feature+refactor+infra patches
Applied to files:
openspec/config.yaml
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Follow `.github/pull_request_template.md` and keep PR descriptions concrete with problem, change, non-goals, risk, and rollback information
Applied to files:
openspec/config.yaml
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: If uncertain, leave a concrete TODO with verification context instead of hidden guesses
Applied to files:
openspec/config.yaml
📚 Learning: 2026-02-17T12:31:17.076Z
Learnt from: CR
Repo: dallay/corvus PR: 0
File: clients/agent-runtime/AGENTS.md:0-0
Timestamp: 2026-02-17T12:31:17.076Z
Learning: Applies to clients/agent-runtime/src/channels/**/*.rs : Implement `Channel` trait in `src/channels/` with consistent `send`, `listen`, and `health_check` semantics and cover auth/allowlist/health behavior with tests
Applied to files:
openspec/specs/sessions/spec.mdopenspec/changes/archive/2026-03-28-session-memory-visibility/specs/sessions/spec.mdclients/agent-runtime/src/gateway/admin.rs
🪛 LanguageTool
openspec/changes/archive/2026-03-28-session-memory-visibility/verify-report.md
[style] ~201-~201: To form a complete sentence, be sure to include a subject.
Context: ... but blocks cargo clippy -D warnings. Should be fixed separately. 3. **Missing i18n ...
(MISSING_IT_THERE)
openspec/changes/archive/2026-03-28-session-memory-visibility/specs/memory-visibility/spec.md
[style] ~24-~24: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...ntries from the local SQLite backend. - MUST support the following query parameters:...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
openspec/specs/sessions/spec.md
[style] ~241-~241: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... return a paginated list of sessions. - MUST support query parameters: - status:...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
[style] ~306-~306: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...ll session record including metadata. - MUST include a summary of memory entries ass...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
openspec/specs/memory-visibility/spec.md
[style] ~25-~25: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...ntries from the local SQLite backend. - MUST support the following query parameters:...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
openspec/changes/archive/2026-03-28-session-memory-visibility/specs/sessions/spec.md
[style] ~240-~240: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... return a paginated list of sessions. - MUST support query parameters: - status:...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
[style] ~305-~305: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...ll session record including metadata. - MUST include a summary of memory entries ass...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
🪛 markdownlint-cli2 (0.22.0)
openspec/changes/archive/2026-03-28-session-memory-visibility/exploration.md
[warning] 28-28: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 36-36: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 48-48: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 61-61: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 71-71: Trailing spaces
Expected: 0 or 2; Actual: 3
(MD009, no-trailing-spaces)
| ( | ||
| StatusCode::OK, | ||
| Json(serde_json::to_value(response).unwrap_or_default()), | ||
| ) |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
unwrap_or_default() on serialization masks potential bugs.
If serde_json::to_value(response) fails, the handler returns an empty JSON object with 200 OK. This could mask struct serialization issues. Consider logging or propagating the error.
Proposed fix
- (
- StatusCode::OK,
- Json(serde_json::to_value(response).unwrap_or_default()),
- )
+ match serde_json::to_value(&response) {
+ Ok(json) => (StatusCode::OK, Json(json)),
+ Err(e) => {
+ tracing::error!("serialize session list failed: {e:#}");
+ (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({"error": "Serialization failed"})))
+ }
+ }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@clients/agent-runtime/src/gateway/admin.rs` around lines 2102 - 2105, The
current handler uses serde_json::to_value(response).unwrap_or_default() which
hides serialization errors by returning an empty JSON with StatusCode::OK;
change this to surface failures by handling the Result from serde_json::to_value
explicitly (e.g., map_err/log the serde_json::Error and return an appropriate
error response instead of OK) so that the Json(...) and StatusCode::OK branch
only runs on successful serialization; update the call sites using
serde_json::to_value, remove unwrap_or_default, and return or log the
serialization error (using the surrounding handler's error type) so failures
aren’t silently masked.
| // Update session activity after message processing | ||
| if let Err(e) = state.mem.update_session_activity(&session_id).await { | ||
| tracing::debug!("session activity update best-effort failed: {e}"); | ||
| } |
There was a problem hiding this comment.
Don't increment session counters on retryable webhook outcomes.
persist_idempotency is false for approval/timeout/error paths, but update_session_activity() still runs afterward. Retrying the same idempotency key will therefore count the same logical turn multiple times, even though upsert_session() has already refreshed the session record.
💡 Suggested fix
let (response, persist_idempotency) =
webhook_response_from_dispatch_result(dispatch_result);
release_idempotency_key(&state, reserved_idempotency_key, persist_idempotency);
-// Update session activity after message processing
-if let Err(e) = state.mem.update_session_activity(&session_id).await {
- tracing::debug!("session activity update best-effort failed: {e}");
+if persist_idempotency {
+ if let Err(e) = state.mem.update_session_activity(&session_id).await {
+ tracing::debug!("session activity update best-effort failed: {e}");
+ }
}
return response;Apply the same guard to the early-return and legacy branches.
Also applies to: 1753-1757, 1774-1777
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@clients/agent-runtime/src/gateway/mod.rs` around lines 1739 - 1742, The code
calls state.mem.update_session_activity(&session_id).await unconditionally,
which increments session counters even when persist_idempotency is false for
retryable webhook outcomes; wrap the update_session_activity call in a guard
that checks the idempotency persistence flag (the same condition used when
calling upsert_session()/persisting idempotency) so it only runs when
persist_idempotency is true, and apply this same guard to the early-return and
legacy branches that currently call update_session_activity (i.e., mirror the
condition around update_session_activity in the existing branch where
upsert_session() is used).
| ### CS-8: Dashboard — Admin TypeScript Types | ||
|
|
||
| The dashboard MUST define TypeScript types for all new API responses. | ||
|
|
||
| - `AdminSessionView`: session list item (id, started_at, ended_at, message_count, last_activity). | ||
| - `AdminSessionDetail`: extends `AdminSessionView` with metadata and memory_summary. | ||
| - `AdminMemoryEntry`: memory entry (id, key, content, category, timestamp, session_id). | ||
| - `AdminMemoryStats`: stats response (total_entries, by_category, total_sessions, active_sessions, backend, cerebro_configured). | ||
| - Types MUST be defined in the existing `admin-config.ts` or a co-located types file. | ||
|
|
||
| #### Scenario: TypeScript types match API response shape | ||
|
|
||
| ```gherkin | ||
| Given the dashboard makes a request to GET /web/admin/sessions | ||
| When the response is received | ||
| Then the response MUST be parseable as PaginatedResponse<AdminSessionView> | ||
| And all fields defined in AdminSessionView MUST be present |
There was a problem hiding this comment.
AdminSessionView is missing the status field.
CS-1 requires the dashboard to render Active/Ended state, and the session list UI consumes session.status. The CS-8 contract currently defines AdminSessionView without that field, so the documented response shape is incomplete.
💡 Suggested fix
-- `AdminSessionView`: session list item (id, started_at, ended_at, message_count, last_activity).
+- `AdminSessionView`: session list item (id, started_at, ended_at, status, message_count, last_activity).As per coding guidelines "Verify technical accuracy and that docs stay aligned with code changes."
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ### CS-8: Dashboard — Admin TypeScript Types | |
| The dashboard MUST define TypeScript types for all new API responses. | |
| - `AdminSessionView`: session list item (id, started_at, ended_at, message_count, last_activity). | |
| - `AdminSessionDetail`: extends `AdminSessionView` with metadata and memory_summary. | |
| - `AdminMemoryEntry`: memory entry (id, key, content, category, timestamp, session_id). | |
| - `AdminMemoryStats`: stats response (total_entries, by_category, total_sessions, active_sessions, backend, cerebro_configured). | |
| - Types MUST be defined in the existing `admin-config.ts` or a co-located types file. | |
| #### Scenario: TypeScript types match API response shape | |
| ```gherkin | |
| Given the dashboard makes a request to GET /web/admin/sessions | |
| When the response is received | |
| Then the response MUST be parseable as PaginatedResponse<AdminSessionView> | |
| And all fields defined in AdminSessionView MUST be present | |
| ### CS-8: Dashboard — Admin TypeScript Types | |
| The dashboard MUST define TypeScript types for all new API responses. | |
| - `AdminSessionView`: session list item (id, started_at, ended_at, status, message_count, last_activity). | |
| - `AdminSessionDetail`: extends `AdminSessionView` with metadata and memory_summary. | |
| - `AdminMemoryEntry`: memory entry (id, key, content, category, timestamp, session_id). | |
| - `AdminMemoryStats`: stats response (total_entries, by_category, total_sessions, active_sessions, backend, cerebro_configured). | |
| - Types MUST be defined in the existing `admin-config.ts` or a co-located types file. | |
| #### Scenario: TypeScript types match API response shape | |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@openspec/changes/archive/2026-03-28-session-memory-visibility/specs/client-surfaces/spec.md`
around lines 324 - 340, The AdminSessionView type in admin-config.ts is missing
the status field used by the dashboard; update the AdminSessionView type
definition to include a status property (matching the API's enum/string shape
used by the UI, e.g., "active" | "ended" or string) so
PaginatedResponse<AdminSessionView> matches the actual GET /web/admin/sessions
response and the dashboard's session.status usage; ensure any dependent types or
tests referencing AdminSessionView are updated accordingly to reflect the new
status field.
|
|
||
| The chat app MUST include a collapsible session history sidebar. | ||
|
|
||
| - The sidebar MUST list past sessions from `GET /session/list`. |
There was a problem hiding this comment.
The session-list route documented here does not match the shipped web client.
CS-5 and CS-6 still name GET /session/list, but clients/web/apps/chat/src/composables/useGateway.ts calls GET /api/v1/sessions?limit=<...>&offset=<...>. This spec should name the real route or explicitly call out /session/list as an alias; otherwise the contract is inaccurate.
As per coding guidelines "Verify technical accuracy and that docs stay aligned with code changes."
Also applies to: 408-416
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@openspec/specs/client-surfaces/spec.md` at line 380, The spec's session-list
route is out of sync with the web client: change CS-5/CS-6 (and the related
section around lines 408-416) to reference the actual endpoint used by the
client (GET /api/v1/sessions?limit=<...>&offset=<...>) or explicitly state that
GET /session/list is an accepted alias for that endpoint; update the route text
and any examples to match the implementation in
clients/web/apps/chat/src/composables/useGateway.ts so the contract is accurate.
Add session lifecycle tracking, memory inspection endpoints, and visibility UX to dashboard and chat clients. Runtime (Rust): - SessionEntry/MemoryStats structs with 7 new Memory trait methods - SQLite sessions table with full CRUD and token-scoped queries - Stale session auto-close in hygiene pass (24h threshold) - 6 new gateway endpoints (5 admin, 1 end-user) Dashboard (Vue): - useAdmin composable for session/memory API - Session monitoring: list, filters, detail views - Memory browser: list, filters, stats, delete with confirmation - Tabbed navigation (Config | Sessions | Memory) Chat (Vue): - Collapsible session history sidebar with 30s polling - Session switching with message persistence - Graceful 404 fallback for older runtimes Tests: 92 new tests (34 Rust + 58 Vue) Closes #277
640a8c1 to
d316eb5
Compare
|



Add session lifecycle tracking, memory inspection endpoints, and visibility UX to dashboard and chat clients.
Runtime (Rust):
Dashboard (Vue):
Chat (Vue):
Tests: 92 new tests (34 Rust + 58 Vue)
Closes #277