cherry-pick voice announcements and thread summaries#2272
cherry-pick voice announcements and thread summaries#2272webguru-hypha wants to merge 2 commits into
Conversation
Speak localized start/stop notices via the Web Speech API for local recorders and remote participants when room capture notices change. Co-authored-by: Cursor <cursoragent@cursor.com>
Store per-room living summaries refreshed on chat activity and a 30-minute ops sweep, generate them via OpenRouter when configured, surface them in Human Chat and Space Memory, and enqueue coherence signals on refresh. Co-authored-by: Cursor <cursoragent@cursor.com>
WalkthroughThis PR introduces comprehensive thread summary functionality for Matrix rooms with living LLM-based summaries, a signal orchestrator for coherence signal evaluation and emission, call capture with recording and transcript support, and expands org memory to index these new assets across web APIs and client UI. ChangesThread Summaries, Signals, and Call Capture
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested labels
Suggested reviewers
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 23
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
docs/operations/space-memory-production-checklist.md (1)
149-149:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winFix formatting: Line 149 appears empty.
Line 149 shows no content. If this is the end of the file, remove the extra line. If content is missing, add it.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/operations/space-memory-production-checklist.md` at line 149, The markdown file space-memory-production-checklist.md contains an extraneous empty line near the end (appears as a blank line); either remove that stray blank line to tidy the file or, if content was accidentally removed, restore the missing text so the final section is complete and the file no longer ends with an unexpected empty line.packages/core/src/governance/server/fetch-org-memory-asset.ts (1)
569-834:⚠️ Potential issue | 🟠 Major | ⚡ Quick winMissing handler for
'mem'asset key type.The
parseOrgMemoryAssetKeyfunction inorg-memory-asset-key.tsdefines a'mem'variant ({ k: 'mem'; d: number; u?: string }), butfetchOrgMemoryAssethas noelse if (key.k === 'mem')branch. Requests with a'mem'key will fall through to line 836 and return'No fetch URL'.🐛 Proposed fix: add handler for memory document body assets
Add a handler after the
'ts'branch (around line 833):+ } else if (key.k === 'mem') { + // Memory document body — return the document description as markdown + const doc = docs.find((d) => d.id === key.d); + if (!doc) { + return { + access: 'ok', + result: { + ok: false, + error: 'Memory document not found for this space', + code: 'not_found', + }, + }; + } + const body = doc.description?.trim() ?? ''; + const byteLength = Buffer.byteLength(body, 'utf8'); + if (byteLength > max_bytes) { + return { + access: 'ok', + result: { + ok: false, + error: `Response size ${byteLength} exceeds max_bytes`, + code: 'too_large', + }, + }; + } + const { text, truncated } = truncateText(body, MAX_TEXT_CHARS); + return { + access: 'ok', + result: buildSuccessText( + `memory-${doc.id}.md`, + 'text/markdown', + text, + truncated, + byteLength, + ), + }; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/governance/server/fetch-org-memory-asset.ts` around lines 569 - 834, fetchOrgMemoryAsset is missing a branch for key.k === 'mem' even though parseOrgMemoryAssetKey defines the 'mem' variant; add an else if (key.k === 'mem') branch in fetchOrgMemoryAsset that loads the memory doc (e.g., call your existing DB helper to fetch the org memory/document by id using key.d and host.id), extract the document body text, compute byteLength with Buffer.byteLength, check against max_bytes and return the same too_large error shape if exceeded, then truncate with truncateText(MAX_TEXT_CHARS) and return buildSuccessText (set a filename like `org-memory-${key.d}.txt`, MIME 'text/plain') similar to the 'ct'/'ds' handlers, and reuse db, host, max_bytes, MAX_TEXT_CHARS, truncateText, and buildSuccessText symbols from the file.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/web/src/app/api/v1/spaces/`[spaceSlug]/thread-summary/activity/route.ts:
- Around line 91-130: The route currently starts refreshThreadSummary(...) with
void .then(...).catch(...) which performs untracked background work that may be
terminated; change this to either await refreshThreadSummary(...) inside the
route handler or move the work into a durable job trigger so the handler only
performs an awaited durable enqueue; specifically replace the fire-and-forget
call to refreshThreadSummary with an awaited call (or instead immediately
enqueue via enqueueSignalEvaluationFromMemory and await that durable enqueue) so
the runtime doesn't terminate the refresh before it completes.
In `@apps/web/src/app/api/v1/spaces/`[spaceSlug]/thread-summary/route.ts:
- Around line 117-126: The route is not propagating client cancellation to
refreshThreadSummary, so aborted client connections won't stop the Matrix
timeline fetch or LLM work; update the call site that constructs the
refreshResult to pass the incoming request.signal (e.g., include signal:
request.signal in the options) when invoking refreshThreadSummary({ spaceSlug,
matrixRoomId, authToken: auth.bearer, requestUrlForSessionMatrix: request.url,
force: false }, { db }), ensuring refreshThreadSummary receives the AbortSignal
to cancel in-flight work.
- Around line 37-47: space.web3SpaceId is being coerced to Number which can lose
precision and then later re-wrapped to BigInt in checkSpaceAccess; instead
parse/validate it as a bigint (e.g., attempt BigInt(space.web3SpaceId) in a
try/catch or use Number.isSafeInteger if you must keep number) and propagate
that bigint into checkSpaceAccess/authorizeSpace so auth gating uses the exact
on-chain id, and additionally forward the request.signal into
refreshThreadSummary(request.signal) when calling it so in-flight refresh work
can be aborted if the client disconnects.
In `@docs/operations/space-memory-production-checklist.md`:
- Around line 79-82: Update the "Suggested schedule" section by replacing the
ambiguous term "active orgs" with a clear, quantitative definition and examples:
specify measurable criteria such as "active orgs = orgs with > X messages/hour,
> Y unique active users in the last Z minutes, or sustained API requests above
threshold T", and show how to map those metrics to schedules (e.g., every 30
minutes if messages/hour > X, otherwise every 6 hours). Apply the same
clarification to the later occurrence of "active orgs" in the document (the
block around the second schedule mention) so both places use the identical,
concrete criteria and example thresholds for operators to implement.
- Around line 6-22: Add a new "Secret Management Best Practices" section to the
document that covers secure storage, rotation, and incident response for the
listed environment variables (e.g., HYPHA_SPACE_MEMORY_OPS_SECRET,
HYPHA_CALL_ARTIFACT_INGEST_SECRET, HYPHA_MATRIX_ORG_MEMORY_ACCESS_TOKEN);
include concise bullets recommending use of a secret manager (Vercel env vars,
AWS Secrets Manager, etc.), never committing secrets to version control, regular
rotation (e.g., quarterly or on compromise), separate secrets per environment
(prod/staging/dev), and auditing access logs and incident response steps so
operators know how to manage and rotate these keys safely.
- Around line 12-14: The runbook lists endpoints that aren't implemented (GET
/api/v1/ops/space-memory/health and POST
/api/v1/ops/space-memory/refresh-discussions) while only
refresh-thread-summaries exists in apps/web/src/app/api/v1/ops/space-memory;
update docs/operations/space-memory-production-checklist.md to either remove
those two endpoints or explicitly mark them as "planned/coming soon (not
implemented)" so operators won't hit 404s, and keep the existing POST
/api/v1/ops/space-memory/refresh-thread-summaries entry as-is.
- Around line 38-41: The docs reference a POST
/api/v1/spaces/{spaceSlug}/call-artifacts endpoint and
x-hypha-ingest-secret/HYPHA_CALL_ARTIFACT_INGEST_SECRET, but there is no
implementation or module for it; either update the checklist to mark this
ingestion as planned or implement the endpoint and header enforcement: create a
call-artifacts module (matching the imports in
packages/core/src/governance/server) that exports the ingestion handler used by
the server, register a POST /api/v1/spaces/:spaceSlug/call-artifacts route,
validate the incoming x-hypha-ingest-secret header against
process.env.HYPHA_CALL_ARTIFACT_INGEST_SECRET, and ensure existing symbols
listSpaceCallArtifactsBySpaceId and getSpaceCallArtifactById are used/updated to
reference the new module; finally, update the docs to reflect the actual
implemented path or mark it as planned if you opt not to implement now.
In `@packages/core/src/coherence/server/signal-orchestrator.ts`:
- Around line 469-485: Replace the optimistic read-then-update pattern that can
let multiple workers pick the same pending rows with a transactional
select-for-update + skip-locked approach: open a transaction via db.transaction,
SELECT the pending rows using FOR UPDATE SKIP LOCKED against the
signalOrchestratorQueue (so rows are locked and skipped by other workers), then
UPDATE those locked rows to state='processing', increment attempts and set
processingStartedAt/updatedAt within the same transaction and RETURNING the
updated rows; reference the existing db, signalOrchestratorQueue and the update
block to locate where to swap the logic so the select-and-update happen
atomically to avoid duplicate processing.
In `@packages/core/src/governance/server/get-org-memory-by-space-slug.ts`:
- Around line 770-819: The uniqueLocator computation is duplicated between
compactOrgMemoryAssetsForSignal and orgMemoryAssetStableId; extract that logic
into a single helper (e.g., computeOrgMemoryUniqueLocator or
deriveOrgMemoryStableIdPart) and have both compactOrgMemoryAssetsForSignal and
orgMemoryAssetStableId call the helper to compute the same locator/stable-id;
update references to use the helper, preserve the exact fallback/concatenation
order (app_url, mxc_uri, call_recording_id, call_transcript_id,
discussion_summary_id, thread_summary_id, then filename:occurred_at), and ensure
any callers that previously relied on the inline string are unchanged in
behavior.
In `@packages/core/src/matrix/client/hooks/use-space-group-call.ts`:
- Around line 435-465: announceCaptureNotice currently derives the target room
from the reactive roomId, which can differ from the actual captured room used by
finalizeRecording (runtime.recordedRoomId), causing stop notices to be sent to
the wrong room; update announceCaptureNotice to use runtime.recordedRoomId (trim
and validate) as the activeRoomId and guard on that plus client, replace
references to roomId inside announceCaptureNotice, and add
runtime.recordedRoomId (or the runtime object) to the hook's dependency array so
the correct room is used; apply the same change to the other notice-emitting
call sites noted (around the lines referenced) so all capture stop/start notices
go to runtime.recordedRoomId.
In `@packages/core/src/org-memory/build-space-memory-items.ts`:
- Around line 174-178: The function memoryDocumentExcerpt is duplicated (in
build-space-memory-items.ts and get-org-memory-by-space-slug.ts); extract it
into a shared utility (e.g., export a function memoryDocumentExcerpt from a new
utils file under packages/core/src/org-memory or packages/core/src/utils),
import and use that single exported function in both buildSpaceMemoryItems (or
wherever memoryDocumentExcerpt is referenced) and getOrgMemoryBySpaceSlug to
remove the duplicate, and ensure the utility re-exports or uses the existing
stripMarkdown and stripDescription helpers so behavior remains identical.
In `@packages/core/src/org-memory/with-org-memory-asset-keys.ts`:
- Around line 64-70: The current throw in withOrgMemoryAssetKeys (the "throw new
Error(`Missing artifact id for source \"${a.source}\"`)" branch) will abort
processing for a batch when one asset lacks an artifact id; change this to a
non-throwing behavior by either filtering such assets out before returning from
withOrgMemoryAssetKeys (remove items where a.source is
call_recording|call_transcript|discussion_summary|thread_summary and artifact id
is missing) or map them to a sentinel key (e.g., "MISSING_ARTIFACT:<source>" or
similar) so the function returns a complete array/object instead of throwing;
update any callers that expect the thrown error to handle the sentinel or
filtered result accordingly.
In
`@packages/epics/src/common/human-chat-panel/human-chat-panel-thread-summary.tsx`:
- Around line 109-110: The list rendering in HumanChatPanelThreadSummary uses
non-unique keys (summary.bullets.map(... key={bullet})), which can cause React
reconciliation issues when two bullets have identical text; change the key to a
stable per-item identifier such as the map index (e.g., key={index}) or another
unique field for each bullet in the summary.bullets map callback so each <li>
has a unique, stable key.
In `@packages/epics/src/common/human-chat-panel/use-thread-summary.ts`:
- Around line 77-93: The POST in recordThreadSummaryActivity silently ignores
failures; wrap the fetch in try/catch, await the response, and if response.ok is
false throw or return a propagated error containing response.status and await
response.text() (or a JSON error) so callers can detect failures; also catch
network exceptions and rethrow or return them; update the function
(use-thread-summary.ts's recordThreadSummaryActivity) to perform these checks
and propagate errors instead of swallowing them.
In `@packages/epics/src/common/human-right-panel.tsx`:
- Around line 3595-3606: The "Owner" string is hardcoded in the span (inside
human-right-panel.tsx where isOwner and selected are used) and must be
internationalized; replace the literal 'Owner' with a call to the existing
translation function t('signalTeamOwnerLabel'), add the key
"signalTeamOwnerLabel": "Owner" to your locale JSON files, and ensure the same
key is present for all supported locales so the UI uses next-intl consistently.
- Around line 3177-3200: The two hardcoded English messages set in the
isUnknownTokenError handling (currently passed into setComposerError after
matrixRef.current.refreshSession()) must be replaced with next-intl translation
keys; create locale entries (e.g., sessionRefreshedRetrySend and
sessionExpiredReloadPage) in the locale JSONs and update the component to use
the component's next-intl translator (e.g., useTranslations or
intl.formatMessage) to supply the localized strings to setComposerError while
preserving the existing control flow that sets input, reply/edit drafts and
draft attachments.
In `@packages/i18n/src/messages/de.json`:
- Around line 2269-2276: The de.json file contains duplicated translation keys
(callCaptureVoiceStartedRecordingTranscript,
callCaptureVoiceStoppedRecordingTranscript, callCaptureVoiceStartedTranscript,
callCaptureVoiceStoppedTranscript); remove the duplicate declarations so each
key appears only once (keep the correct/desired string and delete the other
duplicate entries) to prevent silent overwrites—locate those keys in
packages/i18n/src/messages/de.json and consolidate or remove the older or
unintended duplicates accordingly.
In `@packages/i18n/src/messages/en.json`:
- Around line 1196-1198: Add the missing German locale key for
AgreementFlow.createAgreementBaseFields.errors.memoryAttachmentsRequired to
restore parity with en.json; open the German messages object (de locale) and add
the same nested key path
AgreementFlow.createAgreementBaseFields.errors.memoryAttachmentsRequired with an
appropriate German translation string (e.g., "Bitte laden Sie mindestens ein
Dokument für dieses Memory hoch.") so the key structure matches across locales.
In `@packages/i18n/src/messages/es.json`:
- Line 797: The "title" JSON entry in the Spanish locale contains English text;
replace its value with a Spanish translation (e.g., change "Build Anything,
Together" to "Construye cualquier cosa, juntos") and scan the same section of
packages/i18n/src/messages/es.json to translate the other remaining English
string noted in the comment so all keys in this block are fully localized.
In `@packages/i18n/src/messages/fr.json`:
- Line 106: The French locale contains two untranslated English strings in the
onboarding block (e.g., the "title" key currently set to "Build Anything,
Together" and the other untranslated key at the noted location); update those
message values to their proper French translations so fr.json matches locale
consistency standards, keeping the JSON key names unchanged (e.g., replace the
value for "title" and the other untranslated key with correct French text) and
ensure punctuation/capitalization mirrors other entries in the file.
In `@packages/i18n/src/messages/pt.json`:
- Line 607: The key "spaceMemory" currently has the English value "Space Memory"
in the Portuguese messages; update the translation value for the "spaceMemory"
JSON key to a proper Portuguese phrase (for example "Memória do Espaço") so the
entry reads: "spaceMemory": "Memória do Espaço" ensuring it matches the
surrounding translation style and JSON formatting.
In `@packages/storage-postgres/migrations/0052_thread_summaries.sql`:
- Line 9: Add a GIN index for the JSONB column to speed up containment/key/value
queries on the bullets array: create an index named thread_summaries_bullets_gin
on the thread_summaries table using gin on the bullets column (only add if your
application will query inside the JSONB rather than reading/writing the whole
value). Ensure the migration includes an IF NOT EXISTS guard to avoid duplicate
index creation and that the index name matches thread_summaries_bullets_gin.
---
Outside diff comments:
In `@docs/operations/space-memory-production-checklist.md`:
- Line 149: The markdown file space-memory-production-checklist.md contains an
extraneous empty line near the end (appears as a blank line); either remove that
stray blank line to tidy the file or, if content was accidentally removed,
restore the missing text so the final section is complete and the file no longer
ends with an unexpected empty line.
In `@packages/core/src/governance/server/fetch-org-memory-asset.ts`:
- Around line 569-834: fetchOrgMemoryAsset is missing a branch for key.k ===
'mem' even though parseOrgMemoryAssetKey defines the 'mem' variant; add an else
if (key.k === 'mem') branch in fetchOrgMemoryAsset that loads the memory doc
(e.g., call your existing DB helper to fetch the org memory/document by id using
key.d and host.id), extract the document body text, compute byteLength with
Buffer.byteLength, check against max_bytes and return the same too_large error
shape if exceeded, then truncate with truncateText(MAX_TEXT_CHARS) and return
buildSuccessText (set a filename like `org-memory-${key.d}.txt`, MIME
'text/plain') similar to the 'ct'/'ds' handlers, and reuse db, host, max_bytes,
MAX_TEXT_CHARS, truncateText, and buildSuccessText symbols from the file.
🪄 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: 3f727d8a-59b7-46a4-8c82-d201ac96a1e8
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml,!pnpm-lock.yaml
📒 Files selected for processing (35)
apps/web/src/app/api/v1/ops/space-memory/refresh-thread-summaries/route.tsapps/web/src/app/api/v1/spaces/[spaceSlug]/thread-summary/activity/route.tsapps/web/src/app/api/v1/spaces/[spaceSlug]/thread-summary/route.tsdocs/operations/space-memory-production-checklist.mdpackages/core/package.jsonpackages/core/src/coherence/server/signal-orchestrator.tspackages/core/src/governance/__tests__/thread-summaries-gates.test.tspackages/core/src/governance/server/fetch-org-memory-asset.tspackages/core/src/governance/server/get-org-memory-by-space-slug.tspackages/core/src/governance/server/index.tspackages/core/src/governance/server/thread-summaries-gates.tspackages/core/src/governance/server/thread-summaries.tspackages/core/src/governance/server/thread-summary-llm.tspackages/core/src/matrix/__tests__/call-capture-voice-announcement.test.tspackages/core/src/matrix/client/hooks/call-capture-voice-announcement.tspackages/core/src/matrix/client/hooks/index.tspackages/core/src/matrix/client/hooks/use-space-group-call.tspackages/core/src/org-memory/build-space-memory-items.tspackages/core/src/org-memory/org-memory-asset-key.tspackages/core/src/org-memory/with-org-memory-asset-keys.tspackages/epics/src/coherence/components/space-memory-section.tsxpackages/epics/src/coherence/components/space-memory-timeline-item.tsxpackages/epics/src/common/global-call-dock-context.tsxpackages/epics/src/common/human-chat-panel/human-chat-panel-thread-summary.tsxpackages/epics/src/common/human-chat-panel/use-thread-summary.tspackages/epics/src/common/human-right-panel.tsxpackages/i18n/src/messages/de.jsonpackages/i18n/src/messages/en.jsonpackages/i18n/src/messages/es.jsonpackages/i18n/src/messages/fr.jsonpackages/i18n/src/messages/pt.jsonpackages/storage-postgres/migrations/0052_thread_summaries.sqlpackages/storage-postgres/migrations/meta/_journal.jsonpackages/storage-postgres/src/schema/index.tspackages/storage-postgres/src/schema/thread-summaries.ts
| void refreshThreadSummary( | ||
| { | ||
| spaceSlug, | ||
| matrixRoomId: payload.matrixRoomId, | ||
| authToken: bearer, | ||
| requestUrlForSessionMatrix: request.url, | ||
| }, | ||
| { db }, | ||
| ) | ||
| .then(async (result) => { | ||
| if (result.ok && !result.skipped) { | ||
| try { | ||
| await enqueueSignalEvaluationFromMemory( | ||
| { | ||
| spaceSlug, | ||
| triggerKind: 'thread_summary', | ||
| metadata: { | ||
| matrixRoomId: payload.matrixRoomId, | ||
| threadSummaryId: result.summary.id, | ||
| }, | ||
| }, | ||
| { db }, | ||
| ); | ||
| } catch (enqueueError) { | ||
| console.error('[thread-summary.activity] enqueue failed', { | ||
| spaceSlug, | ||
| error: | ||
| enqueueError instanceof Error | ||
| ? enqueueError.message | ||
| : String(enqueueError), | ||
| }); | ||
| } | ||
| } | ||
| }) | ||
| .catch((error) => { | ||
| console.error('[thread-summary.activity] refresh failed', { | ||
| spaceSlug, | ||
| error: error instanceof Error ? error.message : String(error), | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Avoid untracked background refresh in thread-summary activity route
apps/web/src/app/api/v1/spaces/[spaceSlug]/thread-summary/activity/route.ts starts refreshThreadSummary(...) via void ... .then(...).catch(...) (lines 91-130) and the response is returned before the promise settles. In request-/serverless-scoped runtimes this work can be cut off, dropping the timeline fetch/summary refresh portion (not just the durable enqueue that happens inside the callback).
Run the refresh via a durable job trigger (or a runtime-supported post-response primitive), or await refreshThreadSummary/ensure the handler only performs an awaited durable enqueue.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/src/app/api/v1/spaces/`[spaceSlug]/thread-summary/activity/route.ts
around lines 91 - 130, The route currently starts refreshThreadSummary(...) with
void .then(...).catch(...) which performs untracked background work that may be
terminated; change this to either await refreshThreadSummary(...) inside the
route handler or move the work into a durable job trigger so the handler only
performs an awaited durable enqueue; specifically replace the fire-and-forget
call to refreshThreadSummary with an awaited call (or instead immediately
enqueue via enqueueSignalEvaluationFromMemory and await that durable enqueue) so
the runtime doesn't terminate the refresh before it completes.
| const spaceId = Number(space.web3SpaceId); | ||
| if (!Number.isFinite(spaceId)) { | ||
| return { | ||
| ok: false, | ||
| response: NextResponse.json( | ||
| { error: 'Space has an invalid on-chain space id' }, | ||
| { status: 403 }, | ||
| ), | ||
| }; | ||
| } | ||
| const { hasAccess, response } = await checkSpaceAccess(request, spaceId); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="apps/web/src/app/api/v1/spaces/[spaceSlug]/thread-summary/route.ts"
echo "== File exists =="
ls -l "$FILE"
echo
echo "== Find web3SpaceId conversion =="
rg -n "web3SpaceId|spaceId\s*=|checkSpaceAccess" "$FILE"
echo
echo "== Show lines 1-220 (or up to 220) =="
nl -ba "$FILE" | sed -n '1,220p'
echo
echo "== Search for refresh/upstream calls and any AbortSignal usage =="
rg -n "refresh|AbortController|AbortSignal|signal:|timeout|setTimeout|fetch\\(|axios\\.|PrivyClient|checkSpaceAccess\\(" "$FILE"
echo
echo "== Show around refresh call (if any matches) =="
# print a bit of context around any refresh matches
rg -n "refresh" "$FILE" | cut -d: -f1 | sort -u | while read -r ln; do
start=$((ln-25)); end=$((ln+60));
echo "--- $FILE:$ln ---"
nl -ba "$FILE" | sed -n "${start},${end}p"
doneRepository: hypha-dao/hypha-web
Length of output: 623
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="apps/web/src/app/api/v1/spaces/[spaceSlug]/thread-summary/route.ts"
echo "== Show numbered file (first 260 lines) =="
cat -n "$FILE" | sed -n '1,260p'
echo
echo "== Search for refresh/upstream calls and AbortSignal/timeout =="
rg -n "refresh|AbortController|AbortSignal|signal:|timeout|setTimeout|fetch\\(|axios\\.|PrivyClient|checkSpaceAccess\\(" "$FILE" || true
echo
echo "== Show around web3SpaceId conversion =="
# locate exact line and show nearby code
LINE=$(rg -n "const spaceId = Number\\(space\\.web3SpaceId\\)" "$FILE" | head -n1 | cut -d: -f1)
start=$((LINE-20)); end=$((LINE+40))
cat -n "$FILE" | sed -n "${start},${end}p"
echo
echo "== Show around refresh call(s) =="
for ln in $(rg -n "refresh|fetch\\(|axios\\." "$FILE" | cut -d: -f1 | sort -u); do
start=$((ln-25)); end=$((ln+60))
echo "--- $FILE:$ln ---"
cat -n "$FILE" | sed -n "${start},${end}p"
doneRepository: hypha-dao/hypha-web
Length of output: 17148
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# locate implementations
rg -n "function\s+refreshThreadSummary|refreshThreadSummary\s*=" -S apps/web/src ../ -g'*.ts' -g'*.tsx' || true
rg -n "refreshThreadSummary" -S apps -g'*.ts' -g'*.tsx' > /tmp/r_refresh.txt || true
head -n 30 /tmp/r_refresh.txt || true
rg -n "checkSpaceAccess" -S . -g'*.ts' -g'*.tsx' > /tmp/r_check.txt || true
head -n 50 /tmp/r_check.txt || true
# open the relevant files (best-effort)
# try to print the first ~200 lines around definitions
echo "== Candidate refreshThreadSummary files =="
cat /tmp/r_refresh.txt | cut -d: -f1 | sort -u | head -n 20
echo
echo "== Candidate checkSpaceAccess files =="
cat /tmp/r_check.txt | cut -d: -f1 | sort -u | head -n 20
# If we can infer likely paths, print them with line numbers (limited range)
REFRESH_FILE="$(cat /tmp/r_refresh.txt | cut -d: -f1 | head -n1 || true)"
CHECK_FILE="$(cat /tmp/r_check.txt | cut -d: -f1 | head -n1 || true)"
if [[ -n "${REFRESH_FILE:-}" && -f "$REFRESH_FILE" ]]; then
echo
echo "== Snippet: $REFRESH_FILE (lines 1-220) =="
sed -n '1,220p' "$REFRESH_FILE" | cat -n
fi
if [[ -n "${CHECK_FILE:-}" && -f "$CHECK_FILE" ]]; then
echo
echo "== Snippet: $CHECK_FILE (lines 1-220) =="
sed -n '1,220p' "$CHECK_FILE" | cat -n
fi
echo
echo "== Search within the repo for AbortSignal usage around these functions =="
rg -n "refreshThreadSummary[\\s\\S]{0,800}AbortSignal|AbortSignal[\\s\\S]{0,800}refreshThreadSummary|request\\.signal|AbortController" -S . -g'*.ts' -g'*.tsx' || trueRepository: hypha-dao/hypha-web
Length of output: 26279
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# inspect check-space-access helper used by this route
FILE="apps/web/src/utils/check-space-access.ts"
echo "== check-space-access.ts (first 260 lines) =="
cat -n "$FILE" | sed -n '1,260p'
echo
echo "== search for spaceId usage patterns in check-space-access.ts =="
rg -n "spaceId|BigInt\\(|Number\\(" "$FILE"
echo
echo "== locate canConvertToBigInt implementation =="
# search in workspace
rg -n "function canConvertToBigInt|export function canConvertToBigInt|canConvertToBigInt\\s*=" -S . -g'*.ts' -g'*.tsx' | head -n 20Repository: hypha-dao/hypha-web
Length of output: 10393
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# canConvertToBigInt impl
FILE1="packages/ui-utils/src/canConvertToBigInt.ts"
echo "== $FILE1 =="
cat -n "$FILE1"
echo
echo "== refreshThreadSummary impl (packages/core/src/governance/server/thread-summaries.ts around function) =="
FILE2="packages/core/src/governance/server/thread-summaries.ts"
LINE=$(rg -n "export async function refreshThreadSummary" "$FILE2" | head -n1 | cut -d: -f1)
start=$((LINE-40)); end=$((LINE+200))
cat -n "$FILE2" | sed -n "${start},${end}p"
echo
echo "== Search for AbortSignal/signal usage inside thread-summaries.ts =="
rg -n "AbortSignal|AbortController|signal\\b|request\\.signal" "$FILE2" || true
echo
echo "== Search for canConvertToBigInt usage (to infer expected input types) =="
rg -n "canConvertToBigInt\\(" apps/web/src packages -S | head -n 30Repository: hypha-dao/hypha-web
Length of output: 11935
Use a safe conversion for space.web3SpaceId and propagate abort signal to thread refresh
authorizeSpace:space.web3SpaceIdis converted withNumber(...)and only checked withNumber.isFinite, which can lose precision;checkSpaceAccessthen converts anumberback viaBigInt(spaceId), so large IDs can break auth gating. Add aNumber.isSafeIntegerguard (or avoidNumberentirely by passingbigint).POST:refreshThreadSummary(...)supportssignal?: AbortSignal, but this route doesn’t passrequest.signal, so upstream fetch/LLM work can’t be cancelled when the client disconnects.
Suggested fix
- const spaceId = Number(space.web3SpaceId);
- if (!Number.isFinite(spaceId)) {
+ const spaceId = Number(space.web3SpaceId);
+ if (!Number.isFinite(spaceId) || !Number.isSafeInteger(spaceId)) {
return {
ok: false,
response: NextResponse.json(
{ error: 'Space has an invalid on-chain space id' },
{ status: 403 },
),
};
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/src/app/api/v1/spaces/`[spaceSlug]/thread-summary/route.ts around
lines 37 - 47, space.web3SpaceId is being coerced to Number which can lose
precision and then later re-wrapped to BigInt in checkSpaceAccess; instead
parse/validate it as a bigint (e.g., attempt BigInt(space.web3SpaceId) in a
try/catch or use Number.isSafeInteger if you must keep number) and propagate
that bigint into checkSpaceAccess/authorizeSpace so auth gating uses the exact
on-chain id, and additionally forward the request.signal into
refreshThreadSummary(request.signal) when calling it so in-flight refresh work
can be aborted if the client disconnects.
| const refreshResult = await refreshThreadSummary( | ||
| { | ||
| spaceSlug, | ||
| matrixRoomId, | ||
| authToken: auth.bearer, | ||
| requestUrlForSessionMatrix: request.url, | ||
| force: false, | ||
| }, | ||
| { db }, | ||
| ); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Show the route handler around the flagged lines
FILE="apps/web/src/app/api/v1/spaces/[spaceSlug]/thread-summary/route.ts"
echo "=== route.ts (context around 117-126) ==="
nl -ba "$FILE" | sed -n '90,150p'
# 2) Find refreshThreadSummary definition + signature
echo
echo "=== Searching for refreshThreadSummary ==="
rg -n "refreshThreadSummary" apps/web/src -S
# 3) If we find the definition file, print its signature/nearby implementation
# We'll locate the first definition match by looking for "function refreshThreadSummary" or "const refreshThreadSummary"
# and then print a reasonable window around it.
DEF_FILE="$(rg -n "((function|const)\\s+refreshThreadSummary|export\\s+async\\s+function\\s+refreshThreadSummary|export\\s+\\{\\s*refreshThreadSummary\\s*\\})" apps/web/src -S --files-with-matches | head -n 1 || true)"
if [[ -n "${DEF_FILE}" ]]; then
echo
echo "=== Candidate definition file: $DEF_FILE ==="
nl -ba "$DEF_FILE" | sed -n '1,260p' | sed -n '1,200p'
else
echo
echo "No direct definition pattern found (may be re-exported)."
fi
# 4) Additionally, try to locate a type/interface for the params object passed to refreshThreadSummary
echo
echo "=== Searching for params type containing 'matrixRoomId' or 'requestUrlForSessionMatrix' ==="
rg -n "requestUrlForSessionMatrix|matrixRoomId|authToken" apps/web/src -S | head -n 80Repository: hypha-dao/hypha-web
Length of output: 166
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="apps/web/src/app/api/v1/spaces/[spaceSlug]/thread-summary/route.ts"
echo "=== route.ts (context around the flagged area) ==="
awk 'NR>=90 && NR<=150 {printf "%d\t%s\n", NR, $0}' "$FILE"
echo
echo "=== Searching for refreshThreadSummary ==="
rg -n "refreshThreadSummary" apps/web/src -S || true
echo
echo "=== Searching for type/signature hints (matrixRoomId / requestUrlForSessionMatrix / signal) ==="
rg -n "matrixRoomId|requestUrlForSessionMatrix|authToken|signal" apps/web/src -S | head -n 200Repository: hypha-dao/hypha-web
Length of output: 23810
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Helper to show file size
FILE1="apps/web/src/app/api/v1/spaces/[spaceSlug]/thread-summary/route.ts"
FILE2="apps/web/src/app/api/v1/spaces/[spaceSlug]/thread-summary/activity/route.ts"
FILE3="apps/web/src/app/api/v1/ops/space-memory/refresh-thread-summaries/route.ts"
echo "=== wc -l ==="
wc -l "$FILE1" "$FILE2" "$FILE3" 2>/dev/null || true
echo
echo "=== route.ts imports + handler signature (top of file) ==="
head -n 80 "$FILE1" | sed -n '1,80p'
echo
echo "=== route.ts around refreshThreadSummary call (115-135) ==="
sed -n '105,140p' "$FILE1"
echo
echo "=== activity/route.ts around refreshThreadSummary call(s) ==="
sed -n '60,140p' "$FILE2"
echo
echo "=== ops/space-memory/refresh-thread-summaries/route.ts around where it passes signal ==="
sed -n '1,220p' "$FILE3"
echo
echo "=== Locate refreshThreadSummary definition (signature) ==="
rg -n "refreshThreadSummary" apps/web/src -S --glob='*.ts' --glob='*.tsx' \
| head -n 200
# More direct: search for a function/type signature that includes AbortSignal or signal in the refreshThreadSummary module.
echo
echo "=== Search for AbortSignal + refreshThreadSummary ==="
rg -n "refreshThreadSummary[\\s\\S]{0,200}(AbortSignal|signal)" apps/web/src -S --glob='*.ts' --glob='*.tsx' || true
echo
echo "=== Search for exported definition patterns ==="
rg -n "export\\s+(async\\s+)?function\\s+refreshThreadSummary|export\\s+\\{[^}]*refreshThreadSummary[^}]*\\}|const\\s+refreshThreadSummary\\s*=" apps/web/src -S --glob='*.ts' --glob='*.tsx' || trueRepository: hypha-dao/hypha-web
Length of output: 11982
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Search for request.signal usage in route handlers ==="
rg -n "request\.signal" apps/web/src/app/api/v1 -S --glob='*.ts'
echo
echo "=== Search for signal: request.signal patterns ==="
rg -n "signal:\\s*request\\.signal" apps/web/src/app/api -S --glob='*.ts'
echo
echo "=== Search for passing 'signal' into refreshThreadSummary elsewhere ==="
rg -n "refreshThreadSummary\\([^\\)]*[\\s\\S]*signal" apps/web/src/app/api -S --glob='*.ts'Repository: hypha-dao/hypha-web
Length of output: 121
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Repo-wide search for refreshThreadSummary ==="
rg -n "refreshThreadSummary" . -S --glob='*.ts' --glob='*.tsx' || true
echo
echo "=== Repo-wide search for 'signal' in refreshThreadSummary options ==="
rg -n "refreshThreadSummary[\\s\\S]*signal|signal[\\s\\S]*refreshThreadSummary" . -S --glob='*.ts' --glob='*.tsx' || true
echo
echo "=== Repo-wide search for definition patterns (export async function / const) ==="
rg -n "export\\s+(async\\s+)?function\\s+refreshThreadSummary|const\\s+refreshThreadSummary\\s*=\\s*|function\\s+refreshThreadSummary" . -S --glob='*.ts' --glob='*.tsx' || true
echo
echo "=== Find core/server module paths that re-export refreshThreadSummary ==="
rg -n "`@hypha-platform/core/server`" apps/web/src -S --glob='*.ts' --glob='*.tsx' || trueRepository: hypha-dao/hypha-web
Length of output: 12641
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="packages/core/src/governance/server/thread-summaries.ts"
echo "=== File line count ==="
wc -l "$FILE"
echo
echo "=== Signature area around refreshThreadSummary ==="
# Print around the reported line (301) with some buffer
sed -n '260,360p' "$FILE" | nl -ba -w2 -s':'
echo
echo "=== Search for AbortSignal usage inside thread-summaries.ts ==="
rg -n "AbortSignal|controller\\.signal|signal\\.|timeout" "$FILE" || true
echo
echo "=== Search for 'signal' in refreshThreadSummary params and call to downstream ==="
rg -n "refreshThreadSummary|signal" "$FILE" | head -n 200Repository: hypha-dao/hypha-web
Length of output: 261
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="packages/core/src/governance/server/thread-summaries.ts"
echo "=== File line count ==="
wc -l "$FILE"
echo
echo "=== Signature area around refreshThreadSummary (lines ~280-340) ==="
awk 'NR>=280 && NR<=340 {printf "%d\t%s\n", NR, $0}' "$FILE"
echo
echo "=== Search for AbortSignal usage inside thread-summaries.ts ==="
rg -n "AbortSignal|controller\\.signal|signal\\.|timeout" "$FILE" || true
echo
echo "=== Search for 'signal' in refreshThreadSummary params and downstream calls ==="
rg -n "refreshThreadSummary\\b|signal" "$FILE" | head -n 200Repository: hypha-dao/hypha-web
Length of output: 2452
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="packages/core/src/governance/server/thread-summaries.ts"
echo "=== Context around line ~120-170 (signal?.aborted usage) ==="
awk 'NR>=110 && NR<=190 {printf "%d\t%s\n", NR, $0}' "$FILE"
echo
echo "=== Context around refreshThreadSummary body (lines ~320-430) ==="
awk 'NR>=300 && NR<=450 {printf "%d\t%s\n", NR, $0}' "$FILE"
echo
echo "=== Context around refreshThreadSummary uses of signal (lines ~340-410 narrowed) ==="
awk 'NR>=330 && NR<=410 {printf "%d\t%s\n", NR, $0}' "$FILE"Repository: hypha-dao/hypha-web
Length of output: 10524
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Search for request.signal usage across repo ==="
rg -n "request\.signal" . -S --glob='*.ts' --glob='*.tsx' || true
echo
echo "=== Search for NextRequest.signal (type hint) usage ==="
rg -n "NextRequest[\\s\\S]{0,200}signal|\\.signal\\s*:\\s*request\\.signal" . -S --glob='*.ts' --glob='*.tsx' || trueRepository: hypha-dao/hypha-web
Length of output: 172
🌐 Web query:
Next.js NextRequest signal AbortSignal request.signal available
💡 Result:
Yes—when you use the App Router (and similarly route handlers/pages handlers), Next.js provides an AbortSignal at request.signal on the NextRequest object, so you can listen for abort when the client disconnects. In Next.js App Router route handlers, the first argument is a NextRequest. NextRequest extends the standard Web Request API, and it is built around a Request whose init includes a signal option, so request.signal is available to your handler code [1]. Next.js has also worked specifically to ensure that in the edge runtime, the request.signal passed to Pages API/App Route handlers aborts when the client disconnects (so you can use request.signal to cancel in-flight work / streams) [2][3]. Practical pattern: - In your route handler, pass request.signal to downstream fetches/operations that accept an AbortSignal, or - Add an abort listener (e.g., req.signal.addEventListener('abort',...)) to stop streaming/cleanup work [4]. Example (App Router route handler): const handler = async (request: NextRequest) => { request.signal.addEventListener('abort', => { // cleanup / stop streaming }) const res = await fetch('https://example.com/stream', { signal: request.signal, }) return res } If you meant something else by “request.signal available” (e.g., whether this exists for Node.js-style request objects like IncomingMessage, or for Server Components), tell me the exact handler type/routing (App Router route.ts, Pages API route, Edge runtime vs Node runtime), and I’ll narrow the answer.
Citations:
- 1: https://nextjs.org/docs/app/api-reference/functions/next-request
- 2: Ensure
edgeruntime Pages API/App Route handlersAbortSignalaborts on client disconnect vercel/next.js#51727 - 3: sebmarkbage/next.js@525ecf4
- 4: help detecting client disconnections in route handlers vercel/next.js#48682
Propagate request cancellation to refreshThreadSummary
refreshThreadSummary takes an optional signal?: AbortSignal and uses it to abort both the Matrix timeline fetch and LLM generation, but this route calls it without a signal, so work can continue after the client disconnects.
File: apps/web/src/app/api/v1/spaces/[spaceSlug]/thread-summary/route.ts (lines 117-126)
Suggested fix
const refreshResult = await refreshThreadSummary(
{
spaceSlug,
matrixRoomId,
authToken: auth.bearer,
requestUrlForSessionMatrix: request.url,
force: false,
+ signal: request.signal,
},
{ db },
);📝 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.
| const refreshResult = await refreshThreadSummary( | |
| { | |
| spaceSlug, | |
| matrixRoomId, | |
| authToken: auth.bearer, | |
| requestUrlForSessionMatrix: request.url, | |
| force: false, | |
| }, | |
| { db }, | |
| ); | |
| const refreshResult = await refreshThreadSummary( | |
| { | |
| spaceSlug, | |
| matrixRoomId, | |
| authToken: auth.bearer, | |
| requestUrlForSessionMatrix: request.url, | |
| force: false, | |
| signal: request.signal, | |
| }, | |
| { db }, | |
| ); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/src/app/api/v1/spaces/`[spaceSlug]/thread-summary/route.ts around
lines 117 - 126, The route is not propagating client cancellation to
refreshThreadSummary, so aborted client connections won't stop the Matrix
timeline fetch or LLM work; update the call site that constructs the
refreshResult to pass the incoming request.signal (e.g., include signal:
request.signal in the options) when invoking refreshThreadSummary({ spaceSlug,
matrixRoomId, authToken: auth.bearer, requestUrlForSessionMatrix: request.url,
force: false }, { db }), ensuring refreshThreadSummary receives the AbortSignal
to cancel in-flight work.
| ## 1) Required Environment Variables | ||
|
|
||
| Set these on the web deployment: | ||
|
|
||
| - `HYPHA_SPACE_MEMORY_OPS_SECRET` | ||
| - Shared secret for ops endpoints: | ||
| - `GET /api/v1/ops/space-memory/health` | ||
| - `POST /api/v1/ops/space-memory/refresh-discussions` | ||
| - `POST /api/v1/ops/space-memory/refresh-thread-summaries` | ||
| - `HYPHA_CALL_ARTIFACT_INGEST_SECRET` | ||
| - Shared secret for call artifact ingestion endpoint: | ||
| - `POST /api/v1/spaces/[spaceSlug]/call-artifacts` | ||
| - `NEXT_PUBLIC_MATRIX_HOMESERVER_URL` | ||
| - Required for Matrix timeline/media fetch and discussion summaries. | ||
| - `HYPHA_MATRIX_ORG_MEMORY_ACCESS_TOKEN` | ||
| - Recommended bot/service token for unattended summary refresh jobs. | ||
|
|
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial | ⚡ Quick win
Consider adding secret management guidance.
The document lists several sensitive secrets but doesn't include guidance on secure storage, rotation, or incident response. Adding a brief section on secret management best practices would strengthen the operational security posture.
🔒 Suggested addition after line 22
### Secret Management Best Practices
- Store secrets in your platform's secret manager (e.g., Vercel Environment Variables, AWS Secrets Manager)
- Never commit secrets to version control
- Rotate secrets quarterly or immediately if compromised
- Use different secrets for production, staging, and development environments
- Audit secret access logs regularly🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/operations/space-memory-production-checklist.md` around lines 6 - 22,
Add a new "Secret Management Best Practices" section to the document that covers
secure storage, rotation, and incident response for the listed environment
variables (e.g., HYPHA_SPACE_MEMORY_OPS_SECRET,
HYPHA_CALL_ARTIFACT_INGEST_SECRET, HYPHA_MATRIX_ORG_MEMORY_ACCESS_TOKEN);
include concise bullets recommending use of a secret manager (Vercel env vars,
AWS Secrets Manager, etc.), never committing secrets to version control, regular
rotation (e.g., quarterly or on compromise), separate secrets per environment
(prod/staging/dev), and auditing access logs and incident response steps so
operators know how to manage and rotate these keys safely.
| - `GET /api/v1/ops/space-memory/health` | ||
| - `POST /api/v1/ops/space-memory/refresh-discussions` | ||
| - `POST /api/v1/ops/space-memory/refresh-thread-summaries` |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Verify ops endpoints exist in the codebase
echo "Searching for health endpoint..."
fd -e ts -e tsx route.ts | xargs rg -l "space-memory/health"
echo -e "\nSearching for refresh-discussions endpoint..."
fd -e ts -e tsx route.ts | xargs rg -l "refresh-discussions"
echo -e "\nSearching for refresh-thread-summaries endpoint..."
fd -e ts -e tsx route.ts | xargs rg -l "refresh-thread-summaries"Repository: hypha-dao/hypha-web
Length of output: 270
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== List ops/space-memory route directories (apps/web/src/app/api/v1/ops/space-memory) =="
ls -la apps/web/src/app/api/v1/ops/space-memory 2>/dev/null || echo "missing directory"
echo
echo "== Search for health route path variants =="
rg -n --hidden --glob '!**/node_modules/**' \
"/api/v1/ops/space-memory/health" \
-S . || true
rg -n --hidden --glob '!**/node_modules/**' \
"space-memory/health" \
-S . || true
echo
echo "== Search for refresh-discussions route path variants =="
rg -n --hidden --glob '!**/node_modules/**' \
"/api/v1/ops/space-memory/refresh-discussions" \
-S . || true
rg -n --hidden --glob '!**/node_modules/**' \
"refresh-discussions" \
-S . || true
echo
echo "== Search for refresh-thread-summaries for context =="
rg -n --hidden --glob '!**/node_modules/**' \
"/api/v1/ops/space-memory/refresh-thread-summaries" \
-S . || trueRepository: hypha-dao/hypha-web
Length of output: 1851
Fix missing space-memory ops endpoints in production runbook (health + refresh-discussions)
docs/operations/space-memory-production-checklist.md documents these endpoints, but there are no corresponding implementations in apps/web/src/app/api/v1/ops/space-memory (only refresh-thread-summaries exists):
GET /api/v1/ops/space-memory/health(lines 12-14, also referenced around 118-120)POST /api/v1/ops/space-memory/refresh-discussions(lines 13 and 87-89)
Update the runbook to remove these calls or clearly mark them as planned/coming soon (since operators would otherwise hit 404s).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/operations/space-memory-production-checklist.md` around lines 12 - 14,
The runbook lists endpoints that aren't implemented (GET
/api/v1/ops/space-memory/health and POST
/api/v1/ops/space-memory/refresh-discussions) while only
refresh-thread-summaries exists in apps/web/src/app/api/v1/ops/space-memory;
update docs/operations/space-memory-production-checklist.md to either remove
those two endpoints or explicitly mark them as "planned/coming soon (not
implemented)" so operators won't hit 404s, and keep the existing POST
/api/v1/ops/space-memory/refresh-thread-summaries entry as-is.
| "errors": { | ||
| "memoryAttachmentsRequired": "Please upload at least one document for this memory." | ||
| }, |
There was a problem hiding this comment.
Keep locale key parity for memoryAttachmentsRequired across en and de.
Line 1197 adds AgreementFlow.createAgreementBaseFields.errors.memoryAttachmentsRequired in English, but the same path is missing in packages/i18n/src/messages/de.json. This introduces locale schema drift for a user-facing validation path.
🔧 Suggested addition in packages/i18n/src/messages/de.json
"proposalContent": "Vorschlagsinhalt",
"proposalContentPlaceholder": "Geben Sie hier Ihren Vorschlagsinhalt ein...",
+ "errors": {
+ "memoryAttachmentsRequired": "Bitte laden Sie mindestens ein Dokument für diese Erinnerung hoch."
+ },
"editor": {As per coding guidelines: Message files are consistent across locales (no missing keys).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/i18n/src/messages/en.json` around lines 1196 - 1198, Add the missing
German locale key for
AgreementFlow.createAgreementBaseFields.errors.memoryAttachmentsRequired to
restore parity with en.json; open the German messages object (de locale) and add
the same nested key path
AgreementFlow.createAgreementBaseFields.errors.memoryAttachmentsRequired with an
appropriate German translation string (e.g., "Bitte laden Sie mindestens ein
Dokument für dieses Memory hoch.") so the key structure matches across locales.
| "continueAdventure": "Tu aventura continúa aquí", | ||
| "subtitle": "Tienes una visión poderosa para el mundo; nosotros te damos las herramientas para hacerla realidad.", | ||
| "aiHero": { | ||
| "title": "Build Anything, Together", |
There was a problem hiding this comment.
Translate remaining English copy in the Spanish locale.
These two strings are still in English while the rest of this section is Spanish, which causes mixed-language UX.
Proposed fix
- "title": "Build Anything, Together",
+ "title": "Construye cualquier cosa, juntos",
...
- "badge": "Simple. Powerful. By design.",
+ "badge": "Simple. Potente. Por diseño.",As per coding guidelines, message files in packages/i18n/** should remain consistent across locales.
Also applies to: 818-818
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/i18n/src/messages/es.json` at line 797, The "title" JSON entry in
the Spanish locale contains English text; replace its value with a Spanish
translation (e.g., change "Build Anything, Together" to "Construye cualquier
cosa, juntos") and scan the same section of packages/i18n/src/messages/es.json
to translate the other remaining English string noted in the comment so all keys
in this block are fully localized.
| "continueAdventure": "Votre aventure continue ici", | ||
| "subtitle": "Vous avez une vision puissante du monde, nous vous donnons les outils pour la concrétiser.", | ||
| "aiHero": { | ||
| "title": "Build Anything, Together", |
There was a problem hiding this comment.
Localize the remaining English strings in the French onboarding block.
Two user-facing strings are still English in fr.json, which breaks locale consistency.
Proposed fix
- "title": "Build Anything, Together",
+ "title": "Construisez tout, ensemble",
...
- "badge": "Simple. Powerful. By design.",
+ "badge": "Simple. Puissant. Par conception.",As per coding guidelines, message files in packages/i18n/** should remain consistent across locales.
Also applies to: 127-127
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/i18n/src/messages/fr.json` at line 106, The French locale contains
two untranslated English strings in the onboarding block (e.g., the "title" key
currently set to "Build Anything, Together" and the other untranslated key at
the noted location); update those message values to their proper French
translations so fr.json matches locale consistency standards, keeping the JSON
key names unchanged (e.g., replace the value for "title" and the other
untranslated key with correct French text) and ensure punctuation/capitalization
mirrors other entries in the file.
| "votingMethod": "Método de votação", | ||
| "entryMethod": "Método de entrada", | ||
| "membershipExit": "Saída de adesão", | ||
| "spaceMemory": "Space Memory", |
There was a problem hiding this comment.
Translate "Space Memory" to Portuguese.
This label appears in English within the Portuguese translation file. All other labels in this section are properly translated.
🌐 Suggested translation
- "spaceMemory": "Space Memory",
+ "spaceMemory": "Memória do Espaço",📝 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.
| "spaceMemory": "Space Memory", | |
| "spaceMemory": "Memória do Espaço", |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/i18n/src/messages/pt.json` at line 607, The key "spaceMemory"
currently has the English value "Space Memory" in the Portuguese messages;
update the translation value for the "spaceMemory" JSON key to a proper
Portuguese phrase (for example "Memória do Espaço") so the entry reads:
"spaceMemory": "Memória do Espaço" ensuring it matches the surrounding
translation style and JSON formatting.
| "coherence_slug" varchar(255), | ||
| "thread_title" varchar(512), | ||
| "summary" text DEFAULT '' NOT NULL, | ||
| "bullets" jsonb DEFAULT '[]'::jsonb NOT NULL, |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial | 💤 Low value
Consider GIN index for JSONB queries.
If the application will query or filter by the contents of the bullets JSONB array (e.g., searching for specific bullet points), consider adding a GIN index:
CREATE INDEX IF NOT EXISTS "thread_summaries_bullets_gin" ON "thread_summaries" USING gin ("bullets");If bullets are only read/written as a whole, the current indexing is sufficient.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/storage-postgres/migrations/0052_thread_summaries.sql` at line 9,
Add a GIN index for the JSONB column to speed up containment/key/value queries
on the bullets array: create an index named thread_summaries_bullets_gin on the
thread_summaries table using gin on the bullets column (only add if your
application will query inside the JSONB rather than reading/writing the whole
value). Ensure the migration includes an IF NOT EXISTS guard to avoid duplicate
index creation and that the index name matches thread_summaries_bullets_gin.
Summary
3f2f3cdac84934923fe9742ea10161b145243591(Zoom-style call capture voice announcements).dcdd6ed33376743b86968db15c68ace73e0b8ec2(living thread summaries with org memory integration).mainwhile preserving the cherry-picked feature changes.Test plan
Made with Cursor
Summary by CodeRabbit
New Features
Documentation