Skip to content

M2: Chat-intent for request access (non-member)#9459

Merged
NgoHarrison merged 1 commit into
feature/trusted-contact-accessfrom
swarm/trusted-contact-2
Feb 26, 2026
Merged

M2: Chat-intent for request access (non-member)#9459
NgoHarrison merged 1 commit into
feature/trusted-contact-accessfrom
swarm/trusted-contact-2

Conversation

@NgoHarrison
Copy link
Copy Markdown
Contributor

@NgoHarrison NgoHarrison commented Feb 26, 2026

When a non-member messages the assistant, in addition to the existing deny reply, the system now notifies the guardian that someone is requesting access. Creates a guardian approval request and emits a notification signal.

Closes #9435
Part of #9432


Open with Devin

Co-Authored-By: Claude <noreply@anthropic.com>
@NgoHarrison NgoHarrison self-assigned this Feb 26, 2026
@NgoHarrison NgoHarrison merged commit 0960b29 into feature/trusted-contact-access Feb 26, 2026
@NgoHarrison NgoHarrison deleted the swarm/trusted-contact-2 branch February 26, 2026 05:22
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 5 additional findings in Devin Review.

Open in Devin Review

Comment on lines +1098 to +1111
createApprovalRequest({
runId: `ingress-access-request-${Date.now()}`,
conversationId: `access-req-${sourceChannel}-${senderExternalUserId}`,
assistantId: canonicalAssistantId,
channel: sourceChannel,
requesterExternalUserId: senderExternalUserId,
requesterChatId: externalChatId,
guardianExternalUserId: binding.guardianExternalUserId,
guardianChatId: binding.guardianDeliveryChatId,
toolName: 'ingress_access_request',
riskLevel: 'access_request',
reason: `${senderIdentifier} is requesting access to the assistant`,
expiresAt: Date.now() + GUARDIAN_APPROVAL_TTL_MS,
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Access-request approval records pollute guardian approval interception, swallowing the guardian's normal messages

When a non-member messages the assistant and a guardian binding exists, notifyGuardianOfAccessRequest creates an approval request row in channelGuardianApprovalRequests with a synthetic conversationId like access-req-telegram-user-unknown-456. This record is returned by getAllPendingApprovalsByGuardianChat (which does not filter by toolName) when the guardian later sends any message to their chat.

How the guardian's messages get swallowed

The approval interception flow in guardian-approval-interception.ts:138-148 calls getAllPendingApprovalsByGuardianChat to find pending approvals for the guardian's chat. The new ingress_access_request approval rows are included in these results because the query only filters on channel, guardianChatId, status, and expiresAt — not on toolName (channel-guardian-store.ts:955-981).

When only the access-request approval is pending (the most common case), it becomes the sole guardianApproval at line 141. The guardian's message is then routed into the approval decision flow. Since the synthetic conversationId (access-req-...) has no corresponding pending interaction registered in the pending-interactions tracker, handleChannelDecision (channel-approvals.ts:133-134) returns { applied: false }. The interception code then returns { handled: true, type: 'stale_ignored' }, consuming the guardian's message without processing it normally.

This means any message the guardian sends while an access request is pending gets silently swallowed — the guardian cannot use the assistant normally. If a real tool approval is also pending alongside the access request, it may cause spurious disambiguation prompts or misdirected decisions.

Impact: Guardian is unable to interact with the assistant whenever a non-member access request is pending (up to 30 minutes per the TTL). All guardian messages are consumed by the approval interception flow and discarded as stale.

Prompt for agents
The ingress_access_request approval requests created by notifyGuardianOfAccessRequest are stored in the same channelGuardianApprovalRequests table as regular tool approval requests. The getAllPendingApprovalsByGuardianChat function in assistant/src/memory/channel-guardian-store.ts (line 955) does not filter by toolName, so these access request records are returned by the guardian approval interception flow in assistant/src/runtime/routes/guardian-approval-interception.ts (lines 112, 139, 231). This causes the guardian's normal messages to be intercepted and consumed as stale approval decisions.

To fix this, either:
1. Add a toolName exclusion filter to getAllPendingApprovalsByGuardianChat (and getPendingApprovalByGuardianChat) to exclude ingress_access_request rows, OR
2. Store access request notifications in a separate table or with a different status/type that is not queried by the approval interception flow, OR
3. Add filtering in guardian-approval-interception.ts to skip approval records where toolName is ingress_access_request when looking up guardian approvals.

Option 1 is the simplest: add a condition like ne(channelGuardianApprovalRequests.toolName, 'ingress_access_request') to the WHERE clauses in getPendingApprovalByGuardianChat (line 853), getAllPendingApprovalsByGuardianChat (line 963), and getPendingApprovalByRequestAndGuardianChat.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e548dc74e2

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +1098 to +1101
createApprovalRequest({
runId: `ingress-access-request-${Date.now()}`,
conversationId: `access-req-${sourceChannel}-${senderExternalUserId}`,
assistantId: canonicalAssistantId,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Make access-request approvals resolvable by guardian decisions

This new access-request record is created without a requestId and points at a synthetic conversation ID, but guardian replies are applied through handleChannelDecision(conversationId, ...), which only succeeds when there is a pending interaction for that conversation. In practice, guardian approve/deny messages for ingress_access_request will not apply and the row stays pending until expiry, so the core approve/deny flow described by this change is non-functional.

Useful? React with 👍 / 👎.

Comment on lines +747 to +750
eq(channelGuardianApprovalRequests.assistantId, assistantId),
eq(channelGuardianApprovalRequests.channel, channel),
eq(channelGuardianApprovalRequests.requesterExternalUserId, requesterExternalUserId),
eq(channelGuardianApprovalRequests.toolName, toolName),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include guardian identity in access-request dedup lookup

The dedup query only keys on assistant/channel/requester/tool, so if guardian binding rotates while a request is still pending, subsequent messages from the same requester are suppressed as duplicates and no new request is created for the new guardian. Because decision handling enforces guardianExternalUserId ownership, the new guardian cannot act on the old request, leaving access requests blocked until expiry.

Useful? React with 👍 / 👎.

NgoHarrison added a commit that referenced this pull request Feb 26, 2026
Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>
NgoHarrison added a commit that referenced this pull request Feb 26, 2026
* docs: add trusted contact access design doc (#9452)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* feat: notify guardian when non-member requests access (#9459)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* feat: handle guardian approval decision for access requests (#9460)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* feat: activate trusted contact on successful verification (#9471)

On successful verification of the 6-digit code, upserts
assistant_ingress_members with status=active and policy=allow.
The requester can immediately send messages after verification.

Key changes:
- validateAndConsumeChallenge now distinguishes guardian vs trusted
  contact verification via verificationType field
- Identity-bound outbound sessions (trusted contacts) no longer
  create guardian bindings
- New template for trusted contact verification success message
- Existing guardian verification flow unchanged (backward compatible)

Closes #9437

Co-authored-by: Harrison Ngo <harrison@vellum.ai>

* feat: add HTTP routes for ingress member/invite management (#9475)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* feat: add trusted contacts management skill (#9479)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* feat: add notification signals for trusted contact lifecycle (#9481)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* docs: channel-agnostic rollout and operator runbook for trusted contacts (#9529)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* fix: wrap notifyGuardianOfAccessRequest in try-catch for graceful degradation (#9641)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* fix: scope notification dedupe key to approval request ID (#9643)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* fix: add requestId to access request approval for callback button routing (#9644)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* fix: restore guardian binding on outbound verification by distinguishing verification purpose (#9695)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* fix: propagate guardian code delivery failures to prevent premature requester notification (#9733)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* fix: replace runtime-port URLs with gateway URLs in trusted-contacts skill (#9756)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

---------

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>
awlevin pushed a commit that referenced this pull request Feb 26, 2026
* docs: add trusted contact access design doc (#9452)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* feat: notify guardian when non-member requests access (#9459)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* feat: handle guardian approval decision for access requests (#9460)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* feat: activate trusted contact on successful verification (#9471)

On successful verification of the 6-digit code, upserts
assistant_ingress_members with status=active and policy=allow.
The requester can immediately send messages after verification.

Key changes:
- validateAndConsumeChallenge now distinguishes guardian vs trusted
  contact verification via verificationType field
- Identity-bound outbound sessions (trusted contacts) no longer
  create guardian bindings
- New template for trusted contact verification success message
- Existing guardian verification flow unchanged (backward compatible)

Closes #9437

Co-authored-by: Harrison Ngo <harrison@vellum.ai>

* feat: add HTTP routes for ingress member/invite management (#9475)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* feat: add trusted contacts management skill (#9479)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* feat: add notification signals for trusted contact lifecycle (#9481)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* docs: channel-agnostic rollout and operator runbook for trusted contacts (#9529)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* fix: wrap notifyGuardianOfAccessRequest in try-catch for graceful degradation (#9641)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* fix: scope notification dedupe key to approval request ID (#9643)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* fix: add requestId to access request approval for callback button routing (#9644)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* fix: restore guardian binding on outbound verification by distinguishing verification purpose (#9695)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* fix: propagate guardian code delivery failures to prevent premature requester notification (#9733)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* fix: replace runtime-port URLs with gateway URLs in trusted-contacts skill (#9756)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

---------

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>
awlevin added a commit that referenced this pull request Feb 27, 2026
…) (#9925)

* M1: Land remote skill policy primitive and enable skills.sh-first baseline (#9789)

* test: land remote skill policy primitive and enable skills.sh-first baseline

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: decouple skills.sh risk check from blockSuspicious gate and coerce unknown risk labels

- Remove the `policy.blockSuspicious &&` guard from the skills.sh risk
  threshold check so `maxSkillsShRisk` is always enforced independently
  of the Clawhub-specific blockSuspicious flag.
- Validate that risk labels are known keys in SKILLS_SH_RISK_RANK and
  coerce unrecognized values to 'unknown' (fail closed) to prevent
  novel/typo'd risk strings from bypassing the threshold.
- Add tests for both fixes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use Object.hasOwn for risk label validation to prevent prototype pollution

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: exclude 'unknown' from valid maxSkillsShRisk threshold values

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

* M2: Add skills.sh discovery with audit report retrieval (#9824)

* feat: add skills.sh discovery with audit report retrieval

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: guard risk-rank lookup against prototype property labels in deriveOverallRisk

Use Object.hasOwn(RISK_RANK, dim.risk) instead of checking RISK_RANK[dim.risk] !== undefined
to prevent inherited prototype properties (toString, constructor, etc.) from bypassing the
fail-closed check. Added test case covering this edge case.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

* M3: Model-mediated security recommendation and user override flow (#9838)

* feat: add model-mediated security recommendation and explicit override flow

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: snapshot override entries and include unrecognized risk labels in flagged providers

---------

Co-authored-by: Claude <noreply@anthropic.com>

* M4: Install skills.sh skills into managed store via assistant flow (#9870)

* feat: install skills.sh skills into managed store via assistant flow

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: skip provenance from integrity hash and validate skill IDs before install

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* M5: Fallback to skills.sh when native capability fails (#9896)

* feat: fallback to skills.sh when native capability fails

Add skills.sh fallback skill and CLI wrapper for the search -> evaluate
-> install workflow. The skill teaches the assistant to discover,
evaluate, and install third-party skills from skills.sh with security
audit visibility and user override.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: set exit code on JSON mode failures and use relative CLI path in SKILL.md

- Add `if (!result.success) process.exitCode = 1` after JSON write in the
  install command so callers can detect failures via exit code even in JSON mode
- Replace hardcoded `assistant/src/skills/skillssh-cli.ts` paths in SKILL.md
  with relative `src/skills/skillssh-cli.ts` so the skill works when the daemon
  runs from the assistant/ directory (and in packaged deployments)
- Add exit code assertion to the JSON mode install failure test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use import.meta.main for direct-execution guard

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat: show installed skill provenance and source links in UI

Add provenance label derivation and source URL construction for installed
skills. Distinguishes Vellum (bundled), third-party (skills.sh/Clawhub),
user, and community skills with source metadata.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: remove dead voice source classifier branch (#9775)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: extend voice mode timeout during tool execution (#9781)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: capture app context for voice mode messages (#9786)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add TTS feedback during computer use escalation in voice mode (#9787)

Co-authored-by: Claude <noreply@anthropic.com>

* Use Sonnet 4.6 instead of Haiku for low-tier responses (#9779)

* fix: render file upload surface inline instead of fallback chip (#9773)

The file_upload surface was classified as a "chip only" surface in
InlineSurfaceRouter, causing it to render as a tiny fallback chip instead
of the full drag-and-drop FileUploadSurfaceView. Moved the view to the
shared module and wired it up in the inline router.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: restore voice source classifier bypass for Fn-hold dictation (#9790)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: rephrase computer-use prompt to avoid narrowing sensitive-data ban (#9791)

Co-authored-by: Claude <noreply@anthropic.com>

* perf: add compound index on memoryItems(scopeId, status) (#9792)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: use selectableText modifier in ChatBubbleTextContent (#9793)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: define standard HTTP error response format (#9794)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add per-bearer-token rate limiting for HTTP API (#9795)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: add deduplication check in notification dispatch (#9796)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add circuit breaker for Qdrant client (#9797)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add FTS index reconciliation mechanism (#9799)

Co-authored-by: Claude <noreply@anthropic.com>

* refactor: extract PermissionChecker class from executor.ts (#9798)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add migration crash recovery for stalled migrations (#9800)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: externalize onnxruntime-node in daemon build to fix dylib loading (#9802)

bun build --compile bundles onnxruntime_binding.node into the binary and
extracts it to a temp dir at runtime, but doesn't extract the sibling
libonnxruntime.1.21.0.dylib — causing dlopen to fail.

Fix by marking onnxruntime-node as --external so the compiled binary
resolves it via node_modules/ at runtime, and copying the package
(with onnxruntime-common) alongside the daemon binary. Also codesigns
the native .node/.dylib files in the app bundle.

Co-authored-by: Claude <noreply@anthropic.com>

* perf: add index on notificationEvents(dedupeKey) (#9804)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: surface Qdrant circuit-breaker state in memory retrieval (#9805)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add per-IP rate limiting fallback for unauthenticated endpoints (#9806)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add /v1/debug introspection endpoint (#9807)

Co-authored-by: Claude <noreply@anthropic.com>

* refactor: extract SecretDetectionHandler class from executor.ts (#9808)

Co-authored-by: Claude <noreply@anthropic.com>

* docs: design HTTP token refresh protocol for iOS client (#9809)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: correct async return types and remove invalid local keyword in build.sh (#9810)

- Fix async functions returning void instead of Promise<void> in session/daemon code
- Remove invalid 'local' keyword used outside function scope in macOS build.sh

Co-authored-by: Claude <noreply@anthropic.com>

* refactor: split channel-delivery-store.ts into focused modules (#9811)

Co-authored-by: Claude <noreply@anthropic.com>

* refactor: migrate console.log/error/warn to structured Pino logger (#9812)

Co-authored-by: Claude <noreply@anthropic.com>

* refactor: split channel-guardian-store.ts into focused modules (#9813)

Co-authored-by: Claude <noreply@anthropic.com>

* refactor: split conversation-store.ts into focused modules (#9814)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: prevent voice context prefix from leaking into chat UI, sanitize inputs, capture context earlier (#9815)

Co-authored-by: Claude <noreply@anthropic.com>

* refactor: migrate route error responses to standard format (#9816)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: replace as-unknown-as casts with proper types in core modules (#9817)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: add durable pause flag for conversation timeout during CU escalation (#9818)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: use login names and reactions for safe-do reviewer activity gating (#9819)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: sign all bundled node_modules files and strip non-runtime assets (#9820)

codesign treats everything under Contents/MacOS/ as code objects, so
all files in node_modules — not just native .node/.dylib binaries —
must carry a valid signature. Also strip source files, docs, sourcemaps,
and type declarations that aren't needed at runtime.

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add HTTP request/response logging middleware (#9821)

* fix: sign all bundled node_modules files and strip non-runtime assets

codesign treats everything under Contents/MacOS/ as code objects, so
all files in node_modules — not just native .node/.dylib binaries —
must carry a valid signature. Also strip source files, docs, sourcemaps,
and type declarations that aren't needed at runtime.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: add HTTP request/response logging middleware

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

* fix: await async addMessage calls in tests (#9822)

Co-authored-by: Claude <noreply@anthropic.com>

* refactor: extract ToolApprovalHandler class from executor.ts (#9823)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add provider status to /v1/debug endpoint (#9825)

Co-authored-by: Claude <noreply@anthropic.com>

* perf: add database indexes, FK cascades, job timeout detection, and schema cleanup (#9826)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: capture app context concurrently with transcription, check emptiness after sanitization (#9827)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: replace any types with proper types in http-server WebSocket handlers (#9828)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: destructure handler from webhook factory return values in gateway tests (#9829)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: implement HTTP token refresh in iOS client (#9830)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: reduce thread header spacing and open apps in view-only mode (#9831)

- Shrink Threads heading from 18pt to 13pt
- Remove vertical padding from divider above Threads header
- Reduce Threads header vertical padding from 8pt to 4pt
- Reset isAppChatOpen when opening an app so it defaults to view-only
  mode instead of sticky edit mode

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: await async addMessage/sendMessage calls in tests (#9832)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: check conversationTimeoutPaused in isThinking subscriber (#9833)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: run DictationContextCapture.capture() on MainActor instead of async let (#9835)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: add authenticationRequired case to SessionErrorCategory switch (#9836)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: defer PID file write until daemon is ready (#9839)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: kill stale daemon processes before spawning new one (#9840)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: clean up PID file on daemon startup crash (#9841)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: validate daemon PID is alive in macOS client health check (#9842)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: handle missing onnxruntime-node gracefully in compiled binary (#9843)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: constellation graph — opaque rounded-square nodes, adjust spacing, remove .md files (#9845)

- Replace circle node shapes with rounded squares (RoundedRectangle)
- Add opaque VColor.background fill behind translucent color so edges
  don't show through nodes
- Adjust edge lengths: center→category 200pt, category→subcategory 160pt,
  subcategory→skill 160pt for more breathing room at leaf level
- Remove workspace .md files (IDENTITY.md, USER.md, etc.) from the
  Knowledge category — only actual skills appear in the graph
- Fix exhaustive switch for new authenticationRequired SessionErrorCode

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: resolve TypeScript type-safety issues in work-item handlers and test fixtures (#9846)

- Fix unsafe type assertions in headlessLock cleanup (work-items.ts, work-item-runner.ts)
- Add missing startedAt field to work item test fixtures (memory-regressions.test.ts)
- Fix double-assertion for legacy trust rule migration cast (trust-store.ts)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: add missing authenticationRequired case to ChatErrorToastView switch (#9847)

PR #9836 added the authenticationRequired case to the SessionErrorCategory
enum but missed updating the exhaustive switch in ChatErrorToastView.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: handle authenticationRequired case in macOS and iOS icon switches (#9848)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: write PID file before throwing on daemon startup timeout (#9849)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: add VELLUM_UNSAFE_AUTH_BYPASS to env registry known vars (#9850)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: gracefully degrade when proactive TLS renewal fails (#9851)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: defer semantic search until after early termination check (#9852)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: address review feedback on PR #9771 (#9854)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: address review feedback on PR #9757 (#9855)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: address review feedback on PR #9765 (#9853)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: address review feedback on PR #9753 — restore unsafe-inline for LLM-generated apps, refactor gallery to use addEventListener (#9856)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: auto-deny pending tool confirmations in daemon on new user message (#9788)

* feat: auto-deny pending tool confirmations in daemon when user sends a message

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: move auto-deny to just before dispatchUserMessage

Prevents auto-denying confirmations when the message is intercepted
by secret-block, recording commands, or other early-return paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: auto-deny pending confirmations in HTTP message ingress path too

Ensures the auto-deny behavior works for all clients, not just IPC.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: remove client-side auto-deny, daemon handles it now

The daemon auto-denies pending confirmations when a new user message
arrives, so the client no longer needs to duplicate this logic.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: clear stale pending-interactions entries on auto-deny

When denyAllPendingConfirmations resolves prompter requests directly,
the pending-interactions tracker entries become stale. Clean them up
so channel approval flows don't see phantom pending approvals.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

* fix: address review feedback on PR #9769 (#9857)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: address review feedback on PR #9826 (#9861)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: capture app context before transcription finalization (#9863)

Co-authored-by: Claude <noreply@anthropic.com>

* Trusted Contact Access (Chat-First) (#9530)

* docs: add trusted contact access design doc (#9452)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* feat: notify guardian when non-member requests access (#9459)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* feat: handle guardian approval decision for access requests (#9460)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* feat: activate trusted contact on successful verification (#9471)

On successful verification of the 6-digit code, upserts
assistant_ingress_members with status=active and policy=allow.
The requester can immediately send messages after verification.

Key changes:
- validateAndConsumeChallenge now distinguishes guardian vs trusted
  contact verification via verificationType field
- Identity-bound outbound sessions (trusted contacts) no longer
  create guardian bindings
- New template for trusted contact verification success message
- Existing guardian verification flow unchanged (backward compatible)

Closes #9437

Co-authored-by: Harrison Ngo <harrison@vellum.ai>

* feat: add HTTP routes for ingress member/invite management (#9475)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* feat: add trusted contacts management skill (#9479)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* feat: add notification signals for trusted contact lifecycle (#9481)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* docs: channel-agnostic rollout and operator runbook for trusted contacts (#9529)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* fix: wrap notifyGuardianOfAccessRequest in try-catch for graceful degradation (#9641)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* fix: scope notification dedupe key to approval request ID (#9643)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* fix: add requestId to access request approval for callback button routing (#9644)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* fix: restore guardian binding on outbound verification by distinguishing verification purpose (#9695)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* fix: propagate guardian code delivery failures to prevent premature requester notification (#9733)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* fix: replace runtime-port URLs with gateway URLs in trusted-contacts skill (#9756)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

---------

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* fix: close localhost /v1 runtime URL guard gap (#9859)

* fix: guard PID cleanup to current process on startup failure (#9873)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: use dynamic arch detection in incremental ONNX binary stripping (#9874)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: defer jobs on QdrantCircuitOpenError instead of failing them permanently (#9875)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: include destination in duplicate-delivery lookup (#9876)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: remove schema-only dedupe_key index that is never created at runtime (#9877)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: replace full schedule scan with aggregate SQL counts in debug endpoint (#9878)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: preserve classified risk level on permission-check exceptions (#9879)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: properly handle async rejections from addPointerMessage, persistCallCompletionMessage, and sweepExpiredGuardianActions (#9880)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: address review feedback on PR #9795 (#9881)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: remove redundant scope_id+status index already covered by 3-column index (#9792) (#9882)

Co-authored-by: Claude <noreply@anthropic.com>

* Preserve user-uploaded images across context compaction and retry stripping (#9871)

* fix: use getDbPath() for recovery instructions instead of hardcoded path (#9883)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: address review feedback on PR #9799 (#9884)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: address review feedback on PR #9805 (#9885)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: report 'down' health when no active provider can be selected (#9887)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: address review feedback on PR #9806 (#9888)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: handle undefined response in request logger for WebSocket upgrades (#9889)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: address review feedback on PR #9809 (#9890)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: address review feedback on PR #9816 (#9891)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: accept empty sessionId on session errors as broadcast for 401 recovery (#9892)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: await recursive drainQueue calls to prevent unhandled rejections (#9886)

Co-authored-by: Claude <noreply@anthropic.com>

* Voice ASK_GUARDIAN Generative Timeout + Callback Follow-Up (#9735)

* M1: Guardian Action Follow-Up Data Model + Store Transitions (#9573)

* feat: add guardian action follow-up data model and store transitions

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: require expired status in follow-up state transitions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: restrict terminal follow-up transitions to finalizeFollowup only

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove none->awaiting_guardian_choice from progressFollowupState transitions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add daemon-injected guardian action copy generator (#9604)

Co-authored-by: Claude <noreply@anthropic.com>

* M3: Voice Timeout Flow Migration to Generated Assistant Turn (#9632)

* feat: replace hardcoded voice timeout with generated assistant turn

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: send guardian expiry notices from call-timeout path

When markTimedOutWithReason is called in the call controller's
consultation timeout, the guardian action request transitions from
'pending' to 'expired' immediately. The sweepExpiredGuardianActions
sweep only looks for 'pending' requests, so these requests were never
picked up by the sweep — meaning guardians never received the "question
expired" notice (thread messages, channel replies).

Extract the per-request notification logic from the sweep into a shared
sendGuardianExpiryNotices helper. The call controller timeout path now
captures deliveries before marking the request as timed out, then calls
the helper to send expiry notices to all guardian destinations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

* M4: Late Reply Interception + Generated Guardian Follow-Up Prompt (#9650)

* feat: intercept late guardian replies and initiate follow-up flow

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: address review feedback on guardian timeout PR

- Differentiate guardian_stale_answered vs guardian_stale_expired scenarios
  so answered-from-another-channel gets the correct message instead of the
  generic 'expired' text. Also update the mac path in session-process.ts
  to use the composed message for consistency.

- Gate the expired guardian action late-reply interception on !hasCallbackData
  to prevent inline button presses from being misclassified as late answers.

- Add request-code disambiguation for multiple expired deliveries, mirroring
  the existing pending-delivery disambiguation pattern, so late replies bind
  to the correct expired request.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

* M5: Guardian Follow-Up Conversation Engine (Structured Decisions) (#9669)

* feat: guardian follow-up conversation engine with structured decisions

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: wire generator to mac channel and add followup disambiguation

- Wire guardianFollowUpConversationGenerator to session-process.ts via
  module-level injection from lifecycle.ts, so the mac/IPC channel path
  can classify follow-up replies (previously always returned keep_pending)
- Add request-code disambiguation for multiple awaiting_guardian_choice
  follow-up deliveries in inbound-message-handler.ts, matching the
  existing pattern used for pending and expired deliveries

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

* feat: execute callback/message-back actions and guardian completion replies (#9701)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: harden late-reply interception edge cases (#9706)

* fix: add non-empty guard to voice bullet regression test (#9688)

Add expect(voiceBullet).not.toHaveLength(0) assertion before the negative
assertions to prevent vacuous passes when the voice bullet format changes.

Co-authored-by: Claude <noreply@anthropic.com>

* fix: order pending guardian requests by recency in getPendingRequestByCallSessionId (#9689)

Add ORDER BY created_at DESC to ensure the most recent pending request
is returned when multiple exist for the same call session.

Co-authored-by: Claude <noreply@anthropic.com>

* fix: tighten gateway-only CI guard by removing temporary allowlist (#9690)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: guard pointer emission against invalid origin conversations in relay-server (#9691)

Wrap the cross-conversation pointer write in a try/catch so that a stale
or invalid originConversationId doesn't abort the success/failure handler
and leave the outbound call in a bad state.

Co-authored-by: Claude <noreply@anthropic.com>

* fix: keep polling in safe-do feedback loop when reviews are still pending (#9692)

Instead of treating 'no new reviews within 3 minutes' as done, continue
polling until both reviewers respond or a longer timeout is reached.

Co-authored-by: Claude <noreply@anthropic.com>

* fix: guardian intent routing — use normalized text, fix phone regex, preserve user content (#9693)

- Use normalized text (filler-stripped) for direct guardian pattern matching
- Fix phone regex: phone(?:\s+number)? to match 'verify my phone'
- Preserve original user message when forcing guardian setup flow

Co-authored-by: Claude <noreply@anthropic.com>

* Persist recording-related messages across app restart (#9666)

* M1: Persist original user message for _with_remainder recording intents (#9634)

* feat: persist original user message for _with_remainder recording intents

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: preserve displayContent when slash resolves to unknown

When a _with_remainder message strips down to an unknown slash command,
the unknown-slash branch in session-process.ts was persisting the stripped
content directly instead of using displayContent. For example, "start
recording /foo" would store "/foo" in the DB instead of the original text.

Thread displayContent through the unknown-slash persistence path, mirroring
the pattern used in persistUserMessage in session-messaging.ts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: preserve displayContent in drainQueue unknown-slash path

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Vellum Assistant <assistant@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* M2: Persist user + assistant messages for _only recording commands (#9647)

* feat: persist user + assistant messages for _only recording commands

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: add fallback for msg.task in misc.ts persistence calls

* fix: sync session.messages with persisted recording command messages

After persisting recording command user/assistant messages to SQLite via
conversationStore.addMessage(), also push them to the session's in-memory
messages array. This prevents divergence between DB and in-memory history
that could break operations like regenerate() which rely on both sources.

In sessions.ts the session is always available (created at handler entry).
In misc.ts the session may or may not exist (task_submit creates conversations
not sessions), so a defensive ctx.sessions.get() lookup is used.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Vellum Assistant <assistant@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* M3: Align finalization text and persist error-path messages (#9662)

* feat: align finalization text and persist error-path messages

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: guard error-path addMessage calls against persistence failures

Wrap each error-path conversationStore.addMessage() call in
finalizeAndPublishRecording() with try/catch so the IPC notification
(assistant_text_delta + message_complete) is always sent regardless of
whether the DB write succeeds. Logs a warning if the DB write fails.

This prevents FK violations (conversation deleted), SQLite busy errors,
or other persistence failures from blocking the ctx.send() calls and
causing the function to fail unexpectedly instead of returning
{ success: false }.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Vellum Assistant <assistant@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* fix: guard session.messages push behind processing check to prevent agent loop corruption

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Vellum Assistant <assistant@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* feat: replace timezone free-text input with searchable picker popover (#9696)

Replace the error-prone free-text timezone input with a popover-based
picker that shows a searchable list of all IANA timezones. Includes a
one-click "Use system" button to populate from the Mac's timezone.
Invalid input is now impossible since all selections come from valid
IANA identifiers.

Also fixes a pre-existing Swift build error in ChatBubbleTextContent
where a ternary mixed incompatible TextSelectability types.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: blitz command — use check-pr-reviews for polling, add rebase handling, update README (#9697)

- Use check-pr-reviews script for review polling to detect Codex rate limits
- Add explicit rebase-and-retry for conflicted approved PRs
- Update README slash commands section for new blitz behavior

Co-authored-by: Claude <noreply@anthropic.com>

* fix: address gateway-only guard test feedback from PR #9684 (#9698)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: restore retry logic for transient errors in video fetch after gateway migration (#9699)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: re-trigger reviews after rebase in safe-do conflict resolution (#9700)

After resolving merge conflicts and force-pushing, re-request Codex/Devin
reviews before proceeding to merge, since the post-rebase diff may differ.

Co-authored-by: Claude <noreply@anthropic.com>

* fix: harden late-reply interception in inbound-message-handler

- Skip late-reply interception for callback payloads
- Don't initiate follow-up when answerCall already succeeded
- Handle failed late-followup start instead of falling through
- Add else branch for channel path followup failure with stale message

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jason Zhou <jason@vellum.ai>
Co-authored-by: Vellum Assistant <assistant@vellum.ai>

* fix: check follow-up state transition results before replying to guardian (#9708)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: gate follow-up action execution on successful state transition (#9709)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: address review feedback on guardian follow-up executor - Fix outbound_message_copy fallback to include lateAnswerText and correct audience framing - Fix counterparty resolution to use toNumber for outbound calls (via initiatedFromConversationId heuristic) - Gate follow-up action execution on successful progressFollowupState transition to prevent duplicate side effects (#9710)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: resolve counterparty by call direction and include late answer in outbound SMS fallback (#9713)

Co-authored-by: Claude <noreply@anthropic.com>

* docs: guardian timeout/follow-up architecture docs and hardening guards (#9721)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: address holistic review - remove provider guard, pass mac generator, complete test scenarios

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: pass copy generator to remaining stale followup composer calls in mac path (#9774)

* Remove open_tasks_window IPC message and task_list_show side effect (#9723)

Co-authored-by: Claude <noreply@anthropic.com>

* Update task tool tests for conversation-only output (#9727)

Co-authored-by: Claude <noreply@anthropic.com>

* Update docs to reflect conversation-first task management (#9731)

Co-authored-by: Claude <noreply@anthropic.com>

* Remove macOS standalone Tasks window (#9732)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: add explicit instruction allowing computer use navigation to consoles and dashboards (#9734)

The model's built-in safety training was causing the assistant to refuse
navigating to websites like the Anthropic console for API key creation,
even though the user explicitly asked. The existing 'never type passwords'
rule was being over-extrapolated into 'never go near anything credential-adjacent.'

Add a counter-instruction clarifying that navigating to consoles, dashboards,
login pages, and admin panels is expected behavior when the user asks.

Co-authored-by: Claude <noreply@anthropic.com>

* fix: fall back to any valid codesigning identity before ad-hoc (#9736)

The build script only checked for 'Developer ID Application' and
'Apple Development'/'Mac Developer' certificates. If a self-signed
codesigning certificate (e.g. 'Vellum Development') was the only
identity available, it was skipped and the build fell through to
ad-hoc signing. Ad-hoc signing produces a new code hash on every
rebuild, causing macOS TCC to revoke all granted permissions
(Accessibility, Screen Recording, Microphone, etc.).

Add a generic fallback that picks any valid codesigning identity
before resorting to ad-hoc, so permissions persist across rebuilds.

Co-authored-by: Claude <noreply@anthropic.com>

* Fix test assertion for updated done-status error message (#9741)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: resolve macOS build warnings (#9743)

Co-authored-by: Claude <noreply@anthropic.com>

* Trim redundant words from BOOTSTRAP.md first message (#9740)

Co-authored-by: Claude <noreply@anthropic.com>

* M1: Enrich SOUL.md template (#9737)

* Enrich SOUL.md template with warmth and personality

Co-Authored-By: Claude <noreply@anthropic.com>

* Fix: use asterisk italic to avoid comment stripping

The _ prefix is treated as a comment by stripCommentLines and gets
removed from the system prompt. Using * for italic instead.

---------

Co-authored-by: Claude <noreply@anthropic.com>

* Warm up IDENTITY.md template with inviting field descriptions (#9738)

Co-authored-by: Claude <noreply@anthropic.com>

* Enrich USER.md template with coaching prompts and sign-off (#9739)

Co-authored-by: Claude <noreply@anthropic.com>

* Add memory persistence, group chat, and platform formatting guidance to system prompt (#9742)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: propagate schedule source to task-runner conversations (#9745)

When a schedule triggers a task via run_task:<id>, the task runner
created conversations without source: 'schedule', defaulting to 'user'.
This caused schedule-triggered task threads to appear in the regular
Threads section instead of the Scheduled section in the macOS sidebar.

Add optional source field to TaskRunOptions and pass 'schedule' from
both the scheduler tick and the manual 'run now' handler.

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add startup warning when VELLUM_DAEMON_NOAUTH is set (#9747)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add database index on conversations(updatedAt) (#9748)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add startup warning when DISABLE_HTTP_AUTH is set (#9749)

Co-authored-by: Claude <noreply@anthropic.com>

* chore: rename colliding migration files (026, 027) (#9750)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: add max deferral limit to prevent indefinite job deferral (#9752)

Co-authored-by: Claude <noreply@anthropic.com>

* security: reduce TLS certificate validity from 10 years to 1 year (#9751)

Co-authored-by: Claude <noreply@anthropic.com>

* security: remove unsafe-inline from CSP script-src directive (#9753)

Co-authored-by: Claude <noreply@anthropic.com>

* docs: document why hooks-runner.test.ts is skipped (#9754)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add periodic background cleanup for gateway dedup caches (#9755)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add skill tool name collision detection (#9757)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: add accessibility label to timezone picker clear button (#9758)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: use local file path for recording playback and thumbnails (#9744)

Pass the on-disk file path from daemon to client via IPC so recordings
play directly from the local file instead of fetching via HTTP. The
client generates thumbnails natively with AVAssetImageGenerator,
eliminating the ffmpeg dependency and fixing the 3:4 portrait fallback.

Co-authored-by: Vellum Assistant <assistant@vellum.ai>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: record last_fix_push_time after re-request comments in blitz (#9759)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: use original user text for guardian conversation title generation (#9760)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: pass copy generator to remaining stale followup composer calls in mac path

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Aaron Levin <awlevin@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: siddseethepalli <siddseethepalli@gmail.com>
Co-authored-by: Jason Zhou <jason@vellum.ai>
Co-authored-by: Vellum Assistant <assistant@vellum.ai>

* fix: add missing await on addMessage calls and catch unhandled rejection in sweep

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jason Zhou <jason@vellum.ai>
Co-authored-by: Vellum Assistant <assistant@vellum.ai>
Co-authored-by: Aaron Levin <awlevin@users.noreply.github.com>
Co-authored-by: siddseethepalli <siddseethepalli@gmail.com>

* fix: improve auto-deny UX when user chats during pending confirmation (#9893)

- Update decisionContext to instruct the agent to stop and respond to
  the user's message instead of immediately re-requesting the tool
- Mark confirmation card as denied in the UI when user sends a follow-up

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: index user-uploaded attachments for asset_search (#9895)

* fix: index user-uploaded attachments so asset_search can find them

User attachments (e.g. zip files dropped into chat) were embedded inline
in the message content JSON but never inserted into the attachments table.
This meant asset_search returned nothing and asset_materialize couldn't
locate them. Now persistUserMessage() calls uploadAttachment() +
linkAttachmentToMessage() for each user attachment after persisting the
message, with content-hash dedup to avoid duplicates.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: validate user attachments before indexing

Add validateAttachmentUpload() check before uploadAttachment() to ensure
dangerous file extensions and unsupported MIME types are rejected,
matching the validation that the HTTP upload route already enforces.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address review feedback on PR #9871 (#9894)

* fix: address review feedback on PR #9871

- Restore keep-latest logic for top-level images in media retry so
  context-too-large recovery can actually reduce payload size
- Fix stripContextSummaryTags to find closing tag by indexOf instead of
  endsWith, preventing summary corruption when preserved images append
  trailing text blocks
- Carry forward previously preserved images from existing summary message
  across multiple compaction cycles

* fix: address Codex and Devin review feedback on PR #9894

- Use lastIndexOf for </context_summary> tag to avoid truncating summaries
  that legitimately mention the tag
- Cap preserved image blocks at 5 to prevent unbounded accumulation across
  compaction cycles

* fix: buffer existing cert/key before proactive TLS renewal attempt (#9911)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: remove nonce from CSP style-src to allow unsafe-inline styles (#9912)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: map x86_64 to x64 in ONNX binary stripping for Intel Mac support (#9913)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: skip local embedding backend after onnxruntime import failure (#9915)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: scope stale-daemon kills to current workspace PID (#9917)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: reject custom secret patterns that produce zero-length matches (#9918)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: recreate FTS triggers after dropping them in clearAll (#9916)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: align dedup check with DB unique index on (decision, channel) (#9922)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: persist token to disk before updating in-memory auth state in rotation flow (#9923)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: allow zero candidate budget and conditionally omit degradation notice (#9924)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: change permission status icon from red X to muted circle (#9928)

The red xmark.circle.fill icon looked like a clickable remove button.
Replace with an unfilled circle in textMuted color so it clearly reads
as a 'not yet configured' status indicator.

Co-authored-by: Claude <noreply@anthropic.com>

* fix: inject X-Forwarded-For in gateway proxy and trust it conditionally in runtime (#9926)

Co-authored-by: Claude <noreply@anthropic.com>

* Slack Channel (Socket Mode) — First-Class Channel Ingress/Egress (#9907)

* M1: Gateway Slack config, credentials, and credential watcher (#9872)

* feat: add Slack channel config, credentials, and credential watcher to gateway

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: add slackChannelChanged handler to credential watcher callback in index.ts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: guard Slack credential watcher callback with env var override check

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add /deliver/slack route, schema, and transport hints (#9899)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add Slack Socket Mode adapter and event normalization (#9901)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add Slack channel config endpoints to runtime (#9900)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add Slack channel card to macOS Connect UI (#9903)

Co-authored-by: Claude <noreply@anthropic.com>

* test: add gateway and runtime tests for Slack channel (#9905)

Co-authored-by: Claude <noreply@anthropic.com>

* docs: update architecture docs for Slack channel (#9906)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: check process.env directly for slackFromEnv guard (#9914)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: add JSON body guard and fix OpenAPI schema chatId/to aliasing (#9919)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: remove non-functional guardian row from Slack card and fix xbot- typo (#9920)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: return stored credential state in Slack config error responses (#9921)

Co-authored-by: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add locale-aware time-sent indicator on hover near copy icon (#9910) (#9927)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: prevent multiple threads from appearing active in side nav (#9939)

Rewrite the isSelected logic in threadItem() to use an exhaustive switch
on windowState.selection. Previously, persistentThreadId and selection
were checked independently, so when they pointed to different threads
(e.g. after openConversationThread sets activeThreadId without updating
selection), both threads evaluated as selected. Now, when selection is
explicitly .thread(id) or .appEditing(_, threadId), only that thread is
highlighted. The persistentThreadId fallback only applies when selection
is .app or .none.

Co-authored-by: Claude <noreply@anthropic.com>

* feat: rename Wake Word tab to Voice and add PTT activation key picker (#9940)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add assistant-driven judgement principle to AGENTS.md (#9941)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add permission-aware PTT first-use flow (#9942)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add PTT key indicator to toolbar and menu bar tooltip (#9943)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: move ElevenLabs TTS config into the Voice settings tab (#9944)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add deep-linking to specific settings tabs via IPC (#9945)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: always sync persistentThreadId on active thread change to prevent stale highlight (#9952)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add PTT state to system prompt and channel capabilities (#9953)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: new thread button silently failing when active thread has assistant-only messages (#9955)

The empty-thread-reuse guard in createThread() checked for the absence of
user messages (!vm.messages.contains(where: { .role == .user })). This
caused the guard to fire when the active thread had assistant-only content
(e.g. greetings, notifications, restored sessions) but no user messages
yet. The user would click "+" and nothing would happen.

Tighten the guard to only reuse a thread when it is truly fresh:
vm.messages.isEmpty (no messages at all), no sessionId (not a restored
conversation), and not a private thread.

Co-authored-by: Claude <noreply@anthropic.com>

* fix: mark local embedding backend broken on embed-time init failures (#9956)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: bypass PID liveness gate for custom socket transports and guard cleanup (#9957)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: move FTS trigger recreation after base table DELETEs in clearAll (#9958)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: guard scanText loop against zero-length regex matches (#9959)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: compute degradation notice budget correctly without separator when no candidates (#9960)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: reuse isPrivateAddress for trusted-peer detection in rate limiter (#9961)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: add socket health check to detect alive-but-unresponsive daemons (#9962)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: sort sidebar threads by createdAt instead of lastInteractedAt for stable ordering (#9964)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add IPC message for updating client settings (activation key) (#9963)

Co-authored-by: Claude <noreply@anthropic.com>

* Voice guardian timeout: route remaining copy through daemon (#9908)

* voice: route guardian timeout copy through daemon composer

* fix: address review feedback on guardian timeout copy PR

* fix: preserve token revocation when disk write fails (#9965)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add assistant tool for changing PTT activation key (#9966)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: ensure notification clicks navigate to the correct conversation thread (#9967)

Three issues caused inconsistent notification-to-thread navigation:

1. openConversationThread set activeThreadId but never cleared
   windowState.selection — if the user was viewing a panel, app, or
   settings when clicking a notification, the UI stayed on that view
   instead of showing the chat thread.

2. ACTIVITY_COMPLETE notification handler ignored the sessionId in
   userInfo and just called showMainWindow() without deep-linking.

3. makeViewModel() passed an empty sessionId to activity notifications,
   making deep-linking impossible even if the handler extracted it.

Fix: reset windowState.selection to nil in openConversationThread so
the chat thread is always visible; extract sessionId in the
ACTIVITY_COMPLETE handler; capture a weak viewModel reference to pass
the real sessionId to activity notifications.

Co-authored-by: Claude <noreply@anthropic.com>

* fix: use pendingSettingsTab instead of notification for PTT indicator navigation (#9969)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: only post activationKeyChanged when activation key is updated (#9970)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: remove duplicate ElevenLabs config from Connect tab (#9971)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: differentiate mic vs speech recognition in PTT permission prompt (#9972)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: validate PTT key in system prompt and propagate metadata for queued messages (#9973)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add bundled UPDATES.md template for release bulletins (#9975)

Introduces a release-note source template following the existing
template conventions (comment lines with _ prefix, freeform markdown).
The template will be materialized to ~/.vellum/workspace/UPDATES.md
and used to surface release update notes to the assistant.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add release-block formatter/parser helpers for update bulletin (#9976)

Pure, side-effect-free functions for working with release markers in the
update bulletin file: generating markers, detecting duplicates, appending
new blocks (merge-safe), and extracting version IDs.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add bulletin checkpoint state helpers for active/completed releases (#9977)

Adds read/write helpers for tracking active and completed release
bulletins via memory checkpoints. Includes deduplication, sorting,
and graceful degradation for corrupt checkpoint content.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* test: add ConfigWatcher test harness for workspace prompt file watching (#9978)

Adds deterministic test coverage for ConfigWatcher workspace file watcher
behavior by mocking fs.watch and platform paths. Verifies that prompt file
changes (SOUL.md, IDENTITY.md, USER.md, LOOKS.md) trigger session eviction,
config.json changes trigger config refresh, and unknown files are ignored.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: sort sidebar threads by lastInteractedAt instead of createdAt (#9979)

Threads now move to the top of the sidebar when messages are sent or
received, but NOT when the thread is clicked/selected. The
lastInteractedAt timestamp is already updated on user message send and
assistant message arrival but not on thread selection, making it the
correct sort key for this behavior.

Co-authored-by: Claude <noreply@anthropic.com>

* fix: remove incorrect ctrl+shift mapping and duplicate settings broadcast (#9980)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: restore indentation on showingTrustRules property (#9981)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: add UPDATES.md template contract test (#9982)

* fix: open general Privacy settings when both permissions denied (#9983)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: allow file_read/file_edit/file_write for UPDATES.md (#9984)

Add UPDATES.md to the WORKSPACE_PROMPT_FILES allowlist so the assistant
can read, edit, and write it without prompting. Adds corresponding
permission checker tests.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat: load workspace UPDATES.md into system prompt (#9985)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat: evict sessions on UPDATES.md file changes (#9986)

* feat: add startup sync to materialize current release bulletin (#9987)

PR 12 of the UPDATES.md bulletin rollout plan. Implements
syncUpdateBulletinOnStartup() which reads the bundled UPDATES.md template,
strips comment lines, and appends a release block to the workspace
UPDATES.md for the current APP_VERSION. Skips completed releases and avoids
duplicating existing markers.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add bulletin behavior instructions to system prompt (#9988)

PR 04 of the UPDATES.md bulletin rollout plan. Enhances the updates
section in the system prompt with judgment-based behavioral instructions
for how the assistant should handle update notes — surface relevant
updates naturally, apply assistant-relevant changes silently, and delete
UPDATES.md when all updates have been actioned.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add deletion completion and merge semantics to bulletin sync (#9989)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: deny all pending confirmations in UI, not just the first one (#9904)

Server-side denyAllPending() denies every pending confirmation, but
the client only updated the first one via firstIndex. Use a for loop
to mark all pending confirmations as denied.

Co-authored-by: Claude <noreply@anthropic.com>

* feat: allow rm UPDATES.md in workspace scope (#9990)

Add a default allow rule so the assistant can delete UPDATES.md without
permission friction, mirroring the existing bootstrap delete rule.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: write bearer token file early in daemon startup (#9974)

Move the http-token resolution and file write to right after
ensureDataDir(), before DB init, Qdrant, or any other slow steps.
The CLI polls for this file during gateway startup with a 30s timeout,
which was being exceeded when Qdrant init was slow.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add atomic write path for bulletin sync (#9992)

Replaces direct writeFileSync calls with a temp-file + rename pattern
to prevent partial/truncated UPDATES.md writes if the process crashes
mid-write.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add UPDATES.md to prompt config section (#9993)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: pipe real createdAt through IPC contract for stable thread ordering (#9994)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add thread reuse decision contract and candidate context (M1) (#9995)

Co-authored-by: Claude <noreply@anthropic.com>

* Release v0.3.16 (#9996)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* feat: wire bulletin sync into daemon startup lifecycle (#9997)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use pickup framing for inbound non-guardian voice openers (#9991)

* fix: match inner corner radius to outer in drawer theme toggle (#9998)

Co-authored-by: Claude <noreply@anthropic.com>

* docs: add update bulletin architecture, README, and AGENTS guidance (#10000)

Document the new update bulletin system that surfaces release notes to the
assistant via the system prompt. Adds architecture docs describing the data
flow, checkpoint keys, and key source files. Adds README guidance for release
maintainers. Adds AGENTS.md section on release update hygiene.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove invalid @escaping from tuple return type in PermissionPromptOverlay (#9999)

@escaping is only valid on function parameters, not in tuple return types.
Closures in returned tuples are inherently escaping.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: make createdAt backward-compatible in IPC and HTTP session decoding (#10001)

Co-authored-by: Claude <noreply@anthropic.com>

* perf: batch guardian queries and add destination_conversation_id index in thread-candidates (#10002)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: normalize conversationId and escape titles in thread candidate handling (#10005)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: extract topBarView to resolve Swift type-checker timeout in MainWindowView (#10006)

The coreLayoutView computed property exceeded the Swift compiler's
type-checker complexity limit. Extract the top toolbar into a
separate topBarView property to reduce nesting depth.

Also fix latent .wakeWord enum case (should be .voice) that was
masked by the type-checker timeout.

Co-authored-by: Claude <noreply@anthropic.com>

* feat: update afternoon wake-up greeting to 'Wake up, my friend' (#10007)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: resolve Swift type-checker timeout in MainWindowView (#10008)

Extract topBarView from coreLayoutView to reduce expression complexity
that caused the compiler to bail. Also fix latent .wakeWord reference
(should be .voice) that was hidden by the type-checker timeout.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove duplicate topBarView declaration in MainWindowView (#10009)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat: remove name from all wake-up greetings so assistant is unnamed at hatch (#10010)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: add package.json fallback for APP_VERSION in bulletin sync (#10018)

* test: add atomic write failure path test for bulletin sync (#10019)

Simulates a write failure by making the temp directory read-only,
then verifies that original file content is preserved, no temp file
leftovers remain, and the function throws.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: align empty-template policy across template, tests, and docs (#10020)

Make the bundled UPDATES.md template comment-only so no bulletin is
materialized for releases without explicit update notes. Relax the
contract test to allow empty/comment-only templates. Update bulletin
tests to swap in a test template with real content during setup and
restore the original afterwards, plus add a new test verifying the
comment-only skip path.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: prevent clicking a thread from reordering it to the top (#10013)

When an LRU-evicted ViewModel is re-created on thread click, history
loading triggers the messages Combine publisher which calls
updateLastInteracted, bumping the thread to the top of the sidebar.
Skip updateLastInteracted while history is loading or not yet loaded.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: include assistant name in inbound voice pickup intro (#10022)

* fix: harden getDefaultRuleTemplates against partial config mocks (#10031)

Use optional chaining and runtime type guards in getDefaultRuleTemplates()
so partial config mocks (missing sandbox/skills branches) don't crash rule
generation. Adds a test covering the partial-config path.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: prevent empty thread from getting stuck on loading spinner (#10029)

trimForBackground() blindly reset isHistoryLoaded to false, even for
threads with no session. When switching back, loadHistoryIfNeeded bails
(no sessionId) but isHistoryLoaded stays false, leaving the UI stuck
on a loading spinner. Only reset when there's a session to reload from.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* Migrate browser relay clients to gateway ingress (#9938)

* Route browser relay and clients through gateway

* Exclude browser relay websocket from auth rate limiter

* Address bot feedback on readiness probe and token logging

* Gate browser relay ingress behind runtime proxy flag

* Harden browser relay upgrades to local/private peers

* fix: remove ok:false from slack deliver error response for consistency (#10034)

Co-authored-by: Claude <noreply@anthropic.com>

* docs: clarify warning field is POST-only in Slack config docs (#10035)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: isolate per-table failures in FTS reconciliation (#10039)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: move buffered cert reads inside TLS renewal fallback (#10040)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: reset global regex lastIndex before extractReleaseIds loop (#10041)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: preserve symlink targets in bulletin atomic write (#10042)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: preserve 0.0.0-dev fallback for local development (#10043)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: default sandbox to enabled when config is missing (#10044)

Co-authored-by: Claude <noreply@anthropic.com>

* docs: document token revocation failure semantics (#10045)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: wire up ConfigWatcher test assertions and remove unused locals (#10046)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: match custom socket check with resolveSocketPath semantics (#10047)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: use deterministic mock for bulletin write failure test (#10048)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: harden daemon startup lock and dev command cleanup (#10049)

Co-authored-by: Claude <noreply@anthropic.com>

* Fix browser relay host-spoof bypass in peer guard (#10037)

* fix: decouple bulletin tests from real template file (#10051)

Co-authored-by: Claude <noreply@anthropic.com>

* chore: skip approval prompt before final sweep in safe-blitz (#10056)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: change default host_bash trust policy from ask to allow (#10053) (#10057)

Co-authored-by: Claude <noreply@anthropic.com>

* chore: remove defunct --auto flag from safe-blitz (#10069)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: re-throw aggregate FTS reconciliation failures (#10070)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: update sandbox default test to match new enabled-by-default behavior (#10072)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: handle dangling symlinks in bulletin atomic write (#10071)

Co-authored-by: Claude <noreply@anthropic.com>

* docs: fix daemon-restart token recovery semantics (#10073)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: avoid killing unrelated processes via stale PID files in dev startup (#10074)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: resolve actor isolation warnings in activation key observer (#10081)

Co-authored-by: Claude <noreply@anthropic.com>

* docs: remove --auto references and add deprecated alias in safe-blitz (#10082)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: mark live-streaming threads as history-loaded so assistant activity updates are not dropped (#10086)

Co-authored-…
awlevin added a commit that referenced this pull request Mar 2, 2026
…) (#9925)

* M1: Land remote skill policy primitive and enable skills.sh-first baseline (#9789)

* test: land remote skill policy primitive and enable skills.sh-first baseline

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: decouple skills.sh risk check from blockSuspicious gate and coerce unknown risk labels

- Remove the `policy.blockSuspicious &&` guard from the skills.sh risk
  threshold check so `maxSkillsShRisk` is always enforced independently
  of the Clawhub-specific blockSuspicious flag.
- Validate that risk labels are known keys in SKILLS_SH_RISK_RANK and
  coerce unrecognized values to 'unknown' (fail closed) to prevent
  novel/typo'd risk strings from bypassing the threshold.
- Add tests for both fixes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use Object.hasOwn for risk label validation to prevent prototype pollution

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: exclude 'unknown' from valid maxSkillsShRisk threshold values

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

* M2: Add skills.sh discovery with audit report retrieval (#9824)

* feat: add skills.sh discovery with audit report retrieval

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: guard risk-rank lookup against prototype property labels in deriveOverallRisk

Use Object.hasOwn(RISK_RANK, dim.risk) instead of checking RISK_RANK[dim.risk] !== undefined
to prevent inherited prototype properties (toString, constructor, etc.) from bypassing the
fail-closed check. Added test case covering this edge case.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

* M3: Model-mediated security recommendation and user override flow (#9838)

* feat: add model-mediated security recommendation and explicit override flow

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: snapshot override entries and include unrecognized risk labels in flagged providers

---------

Co-authored-by: Claude <noreply@anthropic.com>

* M4: Install skills.sh skills into managed store via assistant flow (#9870)

* feat: install skills.sh skills into managed store via assistant flow

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: skip provenance from integrity hash and validate skill IDs before install

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* M5: Fallback to skills.sh when native capability fails (#9896)

* feat: fallback to skills.sh when native capability fails

Add skills.sh fallback skill and CLI wrapper for the search -> evaluate
-> install workflow. The skill teaches the assistant to discover,
evaluate, and install third-party skills from skills.sh with security
audit visibility and user override.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: set exit code on JSON mode failures and use relative CLI path in SKILL.md

- Add `if (!result.success) process.exitCode = 1` after JSON write in the
  install command so callers can detect failures via exit code even in JSON mode
- Replace hardcoded `assistant/src/skills/skillssh-cli.ts` paths in SKILL.md
  with relative `src/skills/skillssh-cli.ts` so the skill works when the daemon
  runs from the assistant/ directory (and in packaged deployments)
- Add exit code assertion to the JSON mode install failure test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use import.meta.main for direct-execution guard

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat: show installed skill provenance and source links in UI

Add provenance label derivation and source URL construction for installed
skills. Distinguishes Vellum (bundled), third-party (skills.sh/Clawhub),
user, and community skills with source metadata.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: remove dead voice source classifier branch (#9775)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: extend voice mode timeout during tool execution (#9781)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: capture app context for voice mode messages (#9786)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add TTS feedback during computer use escalation in voice mode (#9787)

Co-authored-by: Claude <noreply@anthropic.com>

* Use Sonnet 4.6 instead of Haiku for low-tier responses (#9779)

* fix: render file upload surface inline instead of fallback chip (#9773)

The file_upload surface was classified as a "chip only" surface in
InlineSurfaceRouter, causing it to render as a tiny fallback chip instead
of the full drag-and-drop FileUploadSurfaceView. Moved the view to the
shared module and wired it up in the inline router.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: restore voice source classifier bypass for Fn-hold dictation (#9790)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: rephrase computer-use prompt to avoid narrowing sensitive-data ban (#9791)

Co-authored-by: Claude <noreply@anthropic.com>

* perf: add compound index on memoryItems(scopeId, status) (#9792)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: use selectableText modifier in ChatBubbleTextContent (#9793)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: define standard HTTP error response format (#9794)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add per-bearer-token rate limiting for HTTP API (#9795)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: add deduplication check in notification dispatch (#9796)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add circuit breaker for Qdrant client (#9797)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add FTS index reconciliation mechanism (#9799)

Co-authored-by: Claude <noreply@anthropic.com>

* refactor: extract PermissionChecker class from executor.ts (#9798)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add migration crash recovery for stalled migrations (#9800)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: externalize onnxruntime-node in daemon build to fix dylib loading (#9802)

bun build --compile bundles onnxruntime_binding.node into the binary and
extracts it to a temp dir at runtime, but doesn't extract the sibling
libonnxruntime.1.21.0.dylib — causing dlopen to fail.

Fix by marking onnxruntime-node as --external so the compiled binary
resolves it via node_modules/ at runtime, and copying the package
(with onnxruntime-common) alongside the daemon binary. Also codesigns
the native .node/.dylib files in the app bundle.

Co-authored-by: Claude <noreply@anthropic.com>

* perf: add index on notificationEvents(dedupeKey) (#9804)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: surface Qdrant circuit-breaker state in memory retrieval (#9805)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add per-IP rate limiting fallback for unauthenticated endpoints (#9806)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add /v1/debug introspection endpoint (#9807)

Co-authored-by: Claude <noreply@anthropic.com>

* refactor: extract SecretDetectionHandler class from executor.ts (#9808)

Co-authored-by: Claude <noreply@anthropic.com>

* docs: design HTTP token refresh protocol for iOS client (#9809)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: correct async return types and remove invalid local keyword in build.sh (#9810)

- Fix async functions returning void instead of Promise<void> in session/daemon code
- Remove invalid 'local' keyword used outside function scope in macOS build.sh

Co-authored-by: Claude <noreply@anthropic.com>

* refactor: split channel-delivery-store.ts into focused modules (#9811)

Co-authored-by: Claude <noreply@anthropic.com>

* refactor: migrate console.log/error/warn to structured Pino logger (#9812)

Co-authored-by: Claude <noreply@anthropic.com>

* refactor: split channel-guardian-store.ts into focused modules (#9813)

Co-authored-by: Claude <noreply@anthropic.com>

* refactor: split conversation-store.ts into focused modules (#9814)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: prevent voice context prefix from leaking into chat UI, sanitize inputs, capture context earlier (#9815)

Co-authored-by: Claude <noreply@anthropic.com>

* refactor: migrate route error responses to standard format (#9816)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: replace as-unknown-as casts with proper types in core modules (#9817)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: add durable pause flag for conversation timeout during CU escalation (#9818)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: use login names and reactions for safe-do reviewer activity gating (#9819)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: sign all bundled node_modules files and strip non-runtime assets (#9820)

codesign treats everything under Contents/MacOS/ as code objects, so
all files in node_modules — not just native .node/.dylib binaries —
must carry a valid signature. Also strip source files, docs, sourcemaps,
and type declarations that aren't needed at runtime.

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add HTTP request/response logging middleware (#9821)

* fix: sign all bundled node_modules files and strip non-runtime assets

codesign treats everything under Contents/MacOS/ as code objects, so
all files in node_modules — not just native .node/.dylib binaries —
must carry a valid signature. Also strip source files, docs, sourcemaps,
and type declarations that aren't needed at runtime.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: add HTTP request/response logging middleware

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

* fix: await async addMessage calls in tests (#9822)

Co-authored-by: Claude <noreply@anthropic.com>

* refactor: extract ToolApprovalHandler class from executor.ts (#9823)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add provider status to /v1/debug endpoint (#9825)

Co-authored-by: Claude <noreply@anthropic.com>

* perf: add database indexes, FK cascades, job timeout detection, and schema cleanup (#9826)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: capture app context concurrently with transcription, check emptiness after sanitization (#9827)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: replace any types with proper types in http-server WebSocket handlers (#9828)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: destructure handler from webhook factory return values in gateway tests (#9829)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: implement HTTP token refresh in iOS client (#9830)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: reduce thread header spacing and open apps in view-only mode (#9831)

- Shrink Threads heading from 18pt to 13pt
- Remove vertical padding from divider above Threads header
- Reduce Threads header vertical padding from 8pt to 4pt
- Reset isAppChatOpen when opening an app so it defaults to view-only
  mode instead of sticky edit mode

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: await async addMessage/sendMessage calls in tests (#9832)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: check conversationTimeoutPaused in isThinking subscriber (#9833)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: run DictationContextCapture.capture() on MainActor instead of async let (#9835)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: add authenticationRequired case to SessionErrorCategory switch (#9836)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: defer PID file write until daemon is ready (#9839)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: kill stale daemon processes before spawning new one (#9840)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: clean up PID file on daemon startup crash (#9841)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: validate daemon PID is alive in macOS client health check (#9842)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: handle missing onnxruntime-node gracefully in compiled binary (#9843)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: constellation graph — opaque rounded-square nodes, adjust spacing, remove .md files (#9845)

- Replace circle node shapes with rounded squares (RoundedRectangle)
- Add opaque VColor.background fill behind translucent color so edges
  don't show through nodes
- Adjust edge lengths: center→category 200pt, category→subcategory 160pt,
  subcategory→skill 160pt for more breathing room at leaf level
- Remove workspace .md files (IDENTITY.md, USER.md, etc.) from the
  Knowledge category — only actual skills appear in the graph
- Fix exhaustive switch for new authenticationRequired SessionErrorCode

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: resolve TypeScript type-safety issues in work-item handlers and test fixtures (#9846)

- Fix unsafe type assertions in headlessLock cleanup (work-items.ts, work-item-runner.ts)
- Add missing startedAt field to work item test fixtures (memory-regressions.test.ts)
- Fix double-assertion for legacy trust rule migration cast (trust-store.ts)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: add missing authenticationRequired case to ChatErrorToastView switch (#9847)

PR #9836 added the authenticationRequired case to the SessionErrorCategory
enum but missed updating the exhaustive switch in ChatErrorToastView.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: handle authenticationRequired case in macOS and iOS icon switches (#9848)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: write PID file before throwing on daemon startup timeout (#9849)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: add VELLUM_UNSAFE_AUTH_BYPASS to env registry known vars (#9850)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: gracefully degrade when proactive TLS renewal fails (#9851)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: defer semantic search until after early termination check (#9852)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: address review feedback on PR #9771 (#9854)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: address review feedback on PR #9757 (#9855)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: address review feedback on PR #9765 (#9853)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: address review feedback on PR #9753 — restore unsafe-inline for LLM-generated apps, refactor gallery to use addEventListener (#9856)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: auto-deny pending tool confirmations in daemon on new user message (#9788)

* feat: auto-deny pending tool confirmations in daemon when user sends a message

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: move auto-deny to just before dispatchUserMessage

Prevents auto-denying confirmations when the message is intercepted
by secret-block, recording commands, or other early-return paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: auto-deny pending confirmations in HTTP message ingress path too

Ensures the auto-deny behavior works for all clients, not just IPC.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: remove client-side auto-deny, daemon handles it now

The daemon auto-denies pending confirmations when a new user message
arrives, so the client no longer needs to duplicate this logic.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: clear stale pending-interactions entries on auto-deny

When denyAllPendingConfirmations resolves prompter requests directly,
the pending-interactions tracker entries become stale. Clean them up
so channel approval flows don't see phantom pending approvals.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

* fix: address review feedback on PR #9769 (#9857)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: address review feedback on PR #9826 (#9861)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: capture app context before transcription finalization (#9863)

Co-authored-by: Claude <noreply@anthropic.com>

* Trusted Contact Access (Chat-First) (#9530)

* docs: add trusted contact access design doc (#9452)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* feat: notify guardian when non-member requests access (#9459)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* feat: handle guardian approval decision for access requests (#9460)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* feat: activate trusted contact on successful verification (#9471)

On successful verification of the 6-digit code, upserts
assistant_ingress_members with status=active and policy=allow.
The requester can immediately send messages after verification.

Key changes:
- validateAndConsumeChallenge now distinguishes guardian vs trusted
  contact verification via verificationType field
- Identity-bound outbound sessions (trusted contacts) no longer
  create guardian bindings
- New template for trusted contact verification success message
- Existing guardian verification flow unchanged (backward compatible)

Closes #9437

Co-authored-by: Harrison Ngo <harrison@vellum.ai>

* feat: add HTTP routes for ingress member/invite management (#9475)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* feat: add trusted contacts management skill (#9479)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* feat: add notification signals for trusted contact lifecycle (#9481)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* docs: channel-agnostic rollout and operator runbook for trusted contacts (#9529)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* fix: wrap notifyGuardianOfAccessRequest in try-catch for graceful degradation (#9641)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* fix: scope notification dedupe key to approval request ID (#9643)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* fix: add requestId to access request approval for callback button routing (#9644)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* fix: restore guardian binding on outbound verification by distinguishing verification purpose (#9695)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* fix: propagate guardian code delivery failures to prevent premature requester notification (#9733)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* fix: replace runtime-port URLs with gateway URLs in trusted-contacts skill (#9756)

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

---------

Co-authored-by: Harrison Ngo <harrison@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* fix: close localhost /v1 runtime URL guard gap (#9859)

* fix: guard PID cleanup to current process on startup failure (#9873)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: use dynamic arch detection in incremental ONNX binary stripping (#9874)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: defer jobs on QdrantCircuitOpenError instead of failing them permanently (#9875)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: include destination in duplicate-delivery lookup (#9876)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: remove schema-only dedupe_key index that is never created at runtime (#9877)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: replace full schedule scan with aggregate SQL counts in debug endpoint (#9878)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: preserve classified risk level on permission-check exceptions (#9879)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: properly handle async rejections from addPointerMessage, persistCallCompletionMessage, and sweepExpiredGuardianActions (#9880)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: address review feedback on PR #9795 (#9881)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: remove redundant scope_id+status index already covered by 3-column index (#9792) (#9882)

Co-authored-by: Claude <noreply@anthropic.com>

* Preserve user-uploaded images across context compaction and retry stripping (#9871)

* fix: use getDbPath() for recovery instructions instead of hardcoded path (#9883)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: address review feedback on PR #9799 (#9884)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: address review feedback on PR #9805 (#9885)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: report 'down' health when no active provider can be selected (#9887)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: address review feedback on PR #9806 (#9888)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: handle undefined response in request logger for WebSocket upgrades (#9889)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: address review feedback on PR #9809 (#9890)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: address review feedback on PR #9816 (#9891)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: accept empty sessionId on session errors as broadcast for 401 recovery (#9892)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: await recursive drainQueue calls to prevent unhandled rejections (#9886)

Co-authored-by: Claude <noreply@anthropic.com>

* Voice ASK_GUARDIAN Generative Timeout + Callback Follow-Up (#9735)

* M1: Guardian Action Follow-Up Data Model + Store Transitions (#9573)

* feat: add guardian action follow-up data model and store transitions

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: require expired status in follow-up state transitions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: restrict terminal follow-up transitions to finalizeFollowup only

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove none->awaiting_guardian_choice from progressFollowupState transitions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add daemon-injected guardian action copy generator (#9604)

Co-authored-by: Claude <noreply@anthropic.com>

* M3: Voice Timeout Flow Migration to Generated Assistant Turn (#9632)

* feat: replace hardcoded voice timeout with generated assistant turn

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: send guardian expiry notices from call-timeout path

When markTimedOutWithReason is called in the call controller's
consultation timeout, the guardian action request transitions from
'pending' to 'expired' immediately. The sweepExpiredGuardianActions
sweep only looks for 'pending' requests, so these requests were never
picked up by the sweep — meaning guardians never received the "question
expired" notice (thread messages, channel replies).

Extract the per-request notification logic from the sweep into a shared
sendGuardianExpiryNotices helper. The call controller timeout path now
captures deliveries before marking the request as timed out, then calls
the helper to send expiry notices to all guardian destinations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

* M4: Late Reply Interception + Generated Guardian Follow-Up Prompt (#9650)

* feat: intercept late guardian replies and initiate follow-up flow

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: address review feedback on guardian timeout PR

- Differentiate guardian_stale_answered vs guardian_stale_expired scenarios
  so answered-from-another-channel gets the correct message instead of the
  generic 'expired' text. Also update the mac path in session-process.ts
  to use the composed message for consistency.

- Gate the expired guardian action late-reply interception on !hasCallbackData
  to prevent inline button presses from being misclassified as late answers.

- Add request-code disambiguation for multiple expired deliveries, mirroring
  the existing pending-delivery disambiguation pattern, so late replies bind
  to the correct expired request.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

* M5: Guardian Follow-Up Conversation Engine (Structured Decisions) (#9669)

* feat: guardian follow-up conversation engine with structured decisions

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: wire generator to mac channel and add followup disambiguation

- Wire guardianFollowUpConversationGenerator to session-process.ts via
  module-level injection from lifecycle.ts, so the mac/IPC channel path
  can classify follow-up replies (previously always returned keep_pending)
- Add request-code disambiguation for multiple awaiting_guardian_choice
  follow-up deliveries in inbound-message-handler.ts, matching the
  existing pattern used for pending and expired deliveries

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

* feat: execute callback/message-back actions and guardian completion replies (#9701)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: harden late-reply interception edge cases (#9706)

* fix: add non-empty guard to voice bullet regression test (#9688)

Add expect(voiceBullet).not.toHaveLength(0) assertion before the negative
assertions to prevent vacuous passes when the voice bullet format changes.

Co-authored-by: Claude <noreply@anthropic.com>

* fix: order pending guardian requests by recency in getPendingRequestByCallSessionId (#9689)

Add ORDER BY created_at DESC to ensure the most recent pending request
is returned when multiple exist for the same call session.

Co-authored-by: Claude <noreply@anthropic.com>

* fix: tighten gateway-only CI guard by removing temporary allowlist (#9690)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: guard pointer emission against invalid origin conversations in relay-server (#9691)

Wrap the cross-conversation pointer write in a try/catch so that a stale
or invalid originConversationId doesn't abort the success/failure handler
and leave the outbound call in a bad state.

Co-authored-by: Claude <noreply@anthropic.com>

* fix: keep polling in safe-do feedback loop when reviews are still pending (#9692)

Instead of treating 'no new reviews within 3 minutes' as done, continue
polling until both reviewers respond or a longer timeout is reached.

Co-authored-by: Claude <noreply@anthropic.com>

* fix: guardian intent routing — use normalized text, fix phone regex, preserve user content (#9693)

- Use normalized text (filler-stripped) for direct guardian pattern matching
- Fix phone regex: phone(?:\s+number)? to match 'verify my phone'
- Preserve original user message when forcing guardian setup flow

Co-authored-by: Claude <noreply@anthropic.com>

* Persist recording-related messages across app restart (#9666)

* M1: Persist original user message for _with_remainder recording intents (#9634)

* feat: persist original user message for _with_remainder recording intents

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: preserve displayContent when slash resolves to unknown

When a _with_remainder message strips down to an unknown slash command,
the unknown-slash branch in session-process.ts was persisting the stripped
content directly instead of using displayContent. For example, "start
recording /foo" would store "/foo" in the DB instead of the original text.

Thread displayContent through the unknown-slash persistence path, mirroring
the pattern used in persistUserMessage in session-messaging.ts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: preserve displayContent in drainQueue unknown-slash path

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Vellum Assistant <assistant@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* M2: Persist user + assistant messages for _only recording commands (#9647)

* feat: persist user + assistant messages for _only recording commands

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: add fallback for msg.task in misc.ts persistence calls

* fix: sync session.messages with persisted recording command messages

After persisting recording command user/assistant messages to SQLite via
conversationStore.addMessage(), also push them to the session's in-memory
messages array. This prevents divergence between DB and in-memory history
that could break operations like regenerate() which rely on both sources.

In sessions.ts the session is always available (created at handler entry).
In misc.ts the session may or may not exist (task_submit creates conversations
not sessions), so a defensive ctx.sessions.get() lookup is used.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Vellum Assistant <assistant@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* M3: Align finalization text and persist error-path messages (#9662)

* feat: align finalization text and persist error-path messages

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: guard error-path addMessage calls against persistence failures

Wrap each error-path conversationStore.addMessage() call in
finalizeAndPublishRecording() with try/catch so the IPC notification
(assistant_text_delta + message_complete) is always sent regardless of
whether the DB write succeeds. Logs a warning if the DB write fails.

This prevents FK violations (conversation deleted), SQLite busy errors,
or other persistence failures from blocking the ctx.send() calls and
causing the function to fail unexpectedly instead of returning
{ success: false }.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Vellum Assistant <assistant@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* fix: guard session.messages push behind processing check to prevent agent loop corruption

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Vellum Assistant <assistant@vellum.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* feat: replace timezone free-text input with searchable picker popover (#9696)

Replace the error-prone free-text timezone input with a popover-based
picker that shows a searchable list of all IANA timezones. Includes a
one-click "Use system" button to populate from the Mac's timezone.
Invalid input is now impossible since all selections come from valid
IANA identifiers.

Also fixes a pre-existing Swift build error in ChatBubbleTextContent
where a ternary mixed incompatible TextSelectability types.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: blitz command — use check-pr-reviews for polling, add rebase handling, update README (#9697)

- Use check-pr-reviews script for review polling to detect Codex rate limits
- Add explicit rebase-and-retry for conflicted approved PRs
- Update README slash commands section for new blitz behavior

Co-authored-by: Claude <noreply@anthropic.com>

* fix: address gateway-only guard test feedback from PR #9684 (#9698)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: restore retry logic for transient errors in video fetch after gateway migration (#9699)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: re-trigger reviews after rebase in safe-do conflict resolution (#9700)

After resolving merge conflicts and force-pushing, re-request Codex/Devin
reviews before proceeding to merge, since the post-rebase diff may differ.

Co-authored-by: Claude <noreply@anthropic.com>

* fix: harden late-reply interception in inbound-message-handler

- Skip late-reply interception for callback payloads
- Don't initiate follow-up when answerCall already succeeded
- Handle failed late-followup start instead of falling through
- Add else branch for channel path followup failure with stale message

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jason Zhou <jason@vellum.ai>
Co-authored-by: Vellum Assistant <assistant@vellum.ai>

* fix: check follow-up state transition results before replying to guardian (#9708)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: gate follow-up action execution on successful state transition (#9709)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: address review feedback on guardian follow-up executor - Fix outbound_message_copy fallback to include lateAnswerText and correct audience framing - Fix counterparty resolution to use toNumber for outbound calls (via initiatedFromConversationId heuristic) - Gate follow-up action execution on successful progressFollowupState transition to prevent duplicate side effects (#9710)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: resolve counterparty by call direction and include late answer in outbound SMS fallback (#9713)

Co-authored-by: Claude <noreply@anthropic.com>

* docs: guardian timeout/follow-up architecture docs and hardening guards (#9721)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: address holistic review - remove provider guard, pass mac generator, complete test scenarios

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: pass copy generator to remaining stale followup composer calls in mac path (#9774)

* Remove open_tasks_window IPC message and task_list_show side effect (#9723)

Co-authored-by: Claude <noreply@anthropic.com>

* Update task tool tests for conversation-only output (#9727)

Co-authored-by: Claude <noreply@anthropic.com>

* Update docs to reflect conversation-first task management (#9731)

Co-authored-by: Claude <noreply@anthropic.com>

* Remove macOS standalone Tasks window (#9732)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: add explicit instruction allowing computer use navigation to consoles and dashboards (#9734)

The model's built-in safety training was causing the assistant to refuse
navigating to websites like the Anthropic console for API key creation,
even though the user explicitly asked. The existing 'never type passwords'
rule was being over-extrapolated into 'never go near anything credential-adjacent.'

Add a counter-instruction clarifying that navigating to consoles, dashboards,
login pages, and admin panels is expected behavior when the user asks.

Co-authored-by: Claude <noreply@anthropic.com>

* fix: fall back to any valid codesigning identity before ad-hoc (#9736)

The build script only checked for 'Developer ID Application' and
'Apple Development'/'Mac Developer' certificates. If a self-signed
codesigning certificate (e.g. 'Vellum Development') was the only
identity available, it was skipped and the build fell through to
ad-hoc signing. Ad-hoc signing produces a new code hash on every
rebuild, causing macOS TCC to revoke all granted permissions
(Accessibility, Screen Recording, Microphone, etc.).

Add a generic fallback that picks any valid codesigning identity
before resorting to ad-hoc, so permissions persist across rebuilds.

Co-authored-by: Claude <noreply@anthropic.com>

* Fix test assertion for updated done-status error message (#9741)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: resolve macOS build warnings (#9743)

Co-authored-by: Claude <noreply@anthropic.com>

* Trim redundant words from BOOTSTRAP.md first message (#9740)

Co-authored-by: Claude <noreply@anthropic.com>

* M1: Enrich SOUL.md template (#9737)

* Enrich SOUL.md template with warmth and personality

Co-Authored-By: Claude <noreply@anthropic.com>

* Fix: use asterisk italic to avoid comment stripping

The _ prefix is treated as a comment by stripCommentLines and gets
removed from the system prompt. Using * for italic instead.

---------

Co-authored-by: Claude <noreply@anthropic.com>

* Warm up IDENTITY.md template with inviting field descriptions (#9738)

Co-authored-by: Claude <noreply@anthropic.com>

* Enrich USER.md template with coaching prompts and sign-off (#9739)

Co-authored-by: Claude <noreply@anthropic.com>

* Add memory persistence, group chat, and platform formatting guidance to system prompt (#9742)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: propagate schedule source to task-runner conversations (#9745)

When a schedule triggers a task via run_task:<id>, the task runner
created conversations without source: 'schedule', defaulting to 'user'.
This caused schedule-triggered task threads to appear in the regular
Threads section instead of the Scheduled section in the macOS sidebar.

Add optional source field to TaskRunOptions and pass 'schedule' from
both the scheduler tick and the manual 'run now' handler.

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add startup warning when VELLUM_DAEMON_NOAUTH is set (#9747)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add database index on conversations(updatedAt) (#9748)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add startup warning when DISABLE_HTTP_AUTH is set (#9749)

Co-authored-by: Claude <noreply@anthropic.com>

* chore: rename colliding migration files (026, 027) (#9750)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: add max deferral limit to prevent indefinite job deferral (#9752)

Co-authored-by: Claude <noreply@anthropic.com>

* security: reduce TLS certificate validity from 10 years to 1 year (#9751)

Co-authored-by: Claude <noreply@anthropic.com>

* security: remove unsafe-inline from CSP script-src directive (#9753)

Co-authored-by: Claude <noreply@anthropic.com>

* docs: document why hooks-runner.test.ts is skipped (#9754)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add periodic background cleanup for gateway dedup caches (#9755)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add skill tool name collision detection (#9757)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: add accessibility label to timezone picker clear button (#9758)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: use local file path for recording playback and thumbnails (#9744)

Pass the on-disk file path from daemon to client via IPC so recordings
play directly from the local file instead of fetching via HTTP. The
client generates thumbnails natively with AVAssetImageGenerator,
eliminating the ffmpeg dependency and fixing the 3:4 portrait fallback.

Co-authored-by: Vellum Assistant <assistant@vellum.ai>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: record last_fix_push_time after re-request comments in blitz (#9759)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: use original user text for guardian conversation title generation (#9760)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: pass copy generator to remaining stale followup composer calls in mac path

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Aaron Levin <awlevin@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: siddseethepalli <siddseethepalli@gmail.com>
Co-authored-by: Jason Zhou <jason@vellum.ai>
Co-authored-by: Vellum Assistant <assistant@vellum.ai>

* fix: add missing await on addMessage calls and catch unhandled rejection in sweep

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jason Zhou <jason@vellum.ai>
Co-authored-by: Vellum Assistant <assistant@vellum.ai>
Co-authored-by: Aaron Levin <awlevin@users.noreply.github.com>
Co-authored-by: siddseethepalli <siddseethepalli@gmail.com>

* fix: improve auto-deny UX when user chats during pending confirmation (#9893)

- Update decisionContext to instruct the agent to stop and respond to
  the user's message instead of immediately re-requesting the tool
- Mark confirmation card as denied in the UI when user sends a follow-up

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: index user-uploaded attachments for asset_search (#9895)

* fix: index user-uploaded attachments so asset_search can find them

User attachments (e.g. zip files dropped into chat) were embedded inline
in the message content JSON but never inserted into the attachments table.
This meant asset_search returned nothing and asset_materialize couldn't
locate them. Now persistUserMessage() calls uploadAttachment() +
linkAttachmentToMessage() for each user attachment after persisting the
message, with content-hash dedup to avoid duplicates.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: validate user attachments before indexing

Add validateAttachmentUpload() check before uploadAttachment() to ensure
dangerous file extensions and unsupported MIME types are rejected,
matching the validation that the HTTP upload route already enforces.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address review feedback on PR #9871 (#9894)

* fix: address review feedback on PR #9871

- Restore keep-latest logic for top-level images in media retry so
  context-too-large recovery can actually reduce payload size
- Fix stripContextSummaryTags to find closing tag by indexOf instead of
  endsWith, preventing summary corruption when preserved images append
  trailing text blocks
- Carry forward previously preserved images from existing summary message
  across multiple compaction cycles

* fix: address Codex and Devin review feedback on PR #9894

- Use lastIndexOf for </context_summary> tag to avoid truncating summaries
  that legitimately mention the tag
- Cap preserved image blocks at 5 to prevent unbounded accumulation across
  compaction cycles

* fix: buffer existing cert/key before proactive TLS renewal attempt (#9911)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: remove nonce from CSP style-src to allow unsafe-inline styles (#9912)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: map x86_64 to x64 in ONNX binary stripping for Intel Mac support (#9913)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: skip local embedding backend after onnxruntime import failure (#9915)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: scope stale-daemon kills to current workspace PID (#9917)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: reject custom secret patterns that produce zero-length matches (#9918)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: recreate FTS triggers after dropping them in clearAll (#9916)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: align dedup check with DB unique index on (decision, channel) (#9922)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: persist token to disk before updating in-memory auth state in rotation flow (#9923)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: allow zero candidate budget and conditionally omit degradation notice (#9924)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: change permission status icon from red X to muted circle (#9928)

The red xmark.circle.fill icon looked like a clickable remove button.
Replace with an unfilled circle in textMuted color so it clearly reads
as a 'not yet configured' status indicator.

Co-authored-by: Claude <noreply@anthropic.com>

* fix: inject X-Forwarded-For in gateway proxy and trust it conditionally in runtime (#9926)

Co-authored-by: Claude <noreply@anthropic.com>

* Slack Channel (Socket Mode) — First-Class Channel Ingress/Egress (#9907)

* M1: Gateway Slack config, credentials, and credential watcher (#9872)

* feat: add Slack channel config, credentials, and credential watcher to gateway

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: add slackChannelChanged handler to credential watcher callback in index.ts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: guard Slack credential watcher callback with env var override check

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add /deliver/slack route, schema, and transport hints (#9899)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add Slack Socket Mode adapter and event normalization (#9901)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add Slack channel config endpoints to runtime (#9900)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add Slack channel card to macOS Connect UI (#9903)

Co-authored-by: Claude <noreply@anthropic.com>

* test: add gateway and runtime tests for Slack channel (#9905)

Co-authored-by: Claude <noreply@anthropic.com>

* docs: update architecture docs for Slack channel (#9906)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: check process.env directly for slackFromEnv guard (#9914)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: add JSON body guard and fix OpenAPI schema chatId/to aliasing (#9919)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: remove non-functional guardian row from Slack card and fix xbot- typo (#9920)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: return stored credential state in Slack config error responses (#9921)

Co-authored-by: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add locale-aware time-sent indicator on hover near copy icon (#9910) (#9927)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: prevent multiple threads from appearing active in side nav (#9939)

Rewrite the isSelected logic in threadItem() to use an exhaustive switch
on windowState.selection. Previously, persistentThreadId and selection
were checked independently, so when they pointed to different threads
(e.g. after openConversationThread sets activeThreadId without updating
selection), both threads evaluated as selected. Now, when selection is
explicitly .thread(id) or .appEditing(_, threadId), only that thread is
highlighted. The persistentThreadId fallback only applies when selection
is .app or .none.

Co-authored-by: Claude <noreply@anthropic.com>

* feat: rename Wake Word tab to Voice and add PTT activation key picker (#9940)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add assistant-driven judgement principle to AGENTS.md (#9941)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add permission-aware PTT first-use flow (#9942)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add PTT key indicator to toolbar and menu bar tooltip (#9943)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: move ElevenLabs TTS config into the Voice settings tab (#9944)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add deep-linking to specific settings tabs via IPC (#9945)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: always sync persistentThreadId on active thread change to prevent stale highlight (#9952)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add PTT state to system prompt and channel capabilities (#9953)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: new thread button silently failing when active thread has assistant-only messages (#9955)

The empty-thread-reuse guard in createThread() checked for the absence of
user messages (!vm.messages.contains(where: { .role == .user })). This
caused the guard to fire when the active thread had assistant-only content
(e.g. greetings, notifications, restored sessions) but no user messages
yet. The user would click "+" and nothing would happen.

Tighten the guard to only reuse a thread when it is truly fresh:
vm.messages.isEmpty (no messages at all), no sessionId (not a restored
conversation), and not a private thread.

Co-authored-by: Claude <noreply@anthropic.com>

* fix: mark local embedding backend broken on embed-time init failures (#9956)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: bypass PID liveness gate for custom socket transports and guard cleanup (#9957)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: move FTS trigger recreation after base table DELETEs in clearAll (#9958)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: guard scanText loop against zero-length regex matches (#9959)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: compute degradation notice budget correctly without separator when no candidates (#9960)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: reuse isPrivateAddress for trusted-peer detection in rate limiter (#9961)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: add socket health check to detect alive-but-unresponsive daemons (#9962)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: sort sidebar threads by createdAt instead of lastInteractedAt for stable ordering (#9964)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add IPC message for updating client settings (activation key) (#9963)

Co-authored-by: Claude <noreply@anthropic.com>

* Voice guardian timeout: route remaining copy through daemon (#9908)

* voice: route guardian timeout copy through daemon composer

* fix: address review feedback on guardian timeout copy PR

* fix: preserve token revocation when disk write fails (#9965)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add assistant tool for changing PTT activation key (#9966)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: ensure notification clicks navigate to the correct conversation thread (#9967)

Three issues caused inconsistent notification-to-thread navigation:

1. openConversationThread set activeThreadId but never cleared
   windowState.selection — if the user was viewing a panel, app, or
   settings when clicking a notification, the UI stayed on that view
   instead of showing the chat thread.

2. ACTIVITY_COMPLETE notification handler ignored the sessionId in
   userInfo and just called showMainWindow() without deep-linking.

3. makeViewModel() passed an empty sessionId to activity notifications,
   making deep-linking impossible even if the handler extracted it.

Fix: reset windowState.selection to nil in openConversationThread so
the chat thread is always visible; extract sessionId in the
ACTIVITY_COMPLETE handler; capture a weak viewModel reference to pass
the real sessionId to activity notifications.

Co-authored-by: Claude <noreply@anthropic.com>

* fix: use pendingSettingsTab instead of notification for PTT indicator navigation (#9969)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: only post activationKeyChanged when activation key is updated (#9970)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: remove duplicate ElevenLabs config from Connect tab (#9971)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: differentiate mic vs speech recognition in PTT permission prompt (#9972)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: validate PTT key in system prompt and propagate metadata for queued messages (#9973)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add bundled UPDATES.md template for release bulletins (#9975)

Introduces a release-note source template following the existing
template conventions (comment lines with _ prefix, freeform markdown).
The template will be materialized to ~/.vellum/workspace/UPDATES.md
and used to surface release update notes to the assistant.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add release-block formatter/parser helpers for update bulletin (#9976)

Pure, side-effect-free functions for working with release markers in the
update bulletin file: generating markers, detecting duplicates, appending
new blocks (merge-safe), and extracting version IDs.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add bulletin checkpoint state helpers for active/completed releases (#9977)

Adds read/write helpers for tracking active and completed release
bulletins via memory checkpoints. Includes deduplication, sorting,
and graceful degradation for corrupt checkpoint content.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* test: add ConfigWatcher test harness for workspace prompt file watching (#9978)

Adds deterministic test coverage for ConfigWatcher workspace file watcher
behavior by mocking fs.watch and platform paths. Verifies that prompt file
changes (SOUL.md, IDENTITY.md, USER.md, LOOKS.md) trigger session eviction,
config.json changes trigger config refresh, and unknown files are ignored.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: sort sidebar threads by lastInteractedAt instead of createdAt (#9979)

Threads now move to the top of the sidebar when messages are sent or
received, but NOT when the thread is clicked/selected. The
lastInteractedAt timestamp is already updated on user message send and
assistant message arrival but not on thread selection, making it the
correct sort key for this behavior.

Co-authored-by: Claude <noreply@anthropic.com>

* fix: remove incorrect ctrl+shift mapping and duplicate settings broadcast (#9980)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: restore indentation on showingTrustRules property (#9981)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: add UPDATES.md template contract test (#9982)

* fix: open general Privacy settings when both permissions denied (#9983)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: allow file_read/file_edit/file_write for UPDATES.md (#9984)

Add UPDATES.md to the WORKSPACE_PROMPT_FILES allowlist so the assistant
can read, edit, and write it without prompting. Adds corresponding
permission checker tests.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat: load workspace UPDATES.md into system prompt (#9985)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat: evict sessions on UPDATES.md file changes (#9986)

* feat: add startup sync to materialize current release bulletin (#9987)

PR 12 of the UPDATES.md bulletin rollout plan. Implements
syncUpdateBulletinOnStartup() which reads the bundled UPDATES.md template,
strips comment lines, and appends a release block to the workspace
UPDATES.md for the current APP_VERSION. Skips completed releases and avoids
duplicating existing markers.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add bulletin behavior instructions to system prompt (#9988)

PR 04 of the UPDATES.md bulletin rollout plan. Enhances the updates
section in the system prompt with judgment-based behavioral instructions
for how the assistant should handle update notes — surface relevant
updates naturally, apply assistant-relevant changes silently, and delete
UPDATES.md when all updates have been actioned.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add deletion completion and merge semantics to bulletin sync (#9989)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: deny all pending confirmations in UI, not just the first one (#9904)

Server-side denyAllPending() denies every pending confirmation, but
the client only updated the first one via firstIndex. Use a for loop
to mark all pending confirmations as denied.

Co-authored-by: Claude <noreply@anthropic.com>

* feat: allow rm UPDATES.md in workspace scope (#9990)

Add a default allow rule so the assistant can delete UPDATES.md without
permission friction, mirroring the existing bootstrap delete rule.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: write bearer token file early in daemon startup (#9974)

Move the http-token resolution and file write to right after
ensureDataDir(), before DB init, Qdrant, or any other slow steps.
The CLI polls for this file during gateway startup with a 30s timeout,
which was being exceeded when Qdrant init was slow.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add atomic write path for bulletin sync (#9992)

Replaces direct writeFileSync calls with a temp-file + rename pattern
to prevent partial/truncated UPDATES.md writes if the process crashes
mid-write.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add UPDATES.md to prompt config section (#9993)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: pipe real createdAt through IPC contract for stable thread ordering (#9994)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: add thread reuse decision contract and candidate context (M1) (#9995)

Co-authored-by: Claude <noreply@anthropic.com>

* Release v0.3.16 (#9996)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* feat: wire bulletin sync into daemon startup lifecycle (#9997)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use pickup framing for inbound non-guardian voice openers (#9991)

* fix: match inner corner radius to outer in drawer theme toggle (#9998)

Co-authored-by: Claude <noreply@anthropic.com>

* docs: add update bulletin architecture, README, and AGENTS guidance (#10000)

Document the new update bulletin system that surfaces release notes to the
assistant via the system prompt. Adds architecture docs describing the data
flow, checkpoint keys, and key source files. Adds README guidance for release
maintainers. Adds AGENTS.md section on release update hygiene.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove invalid @escaping from tuple return type in PermissionPromptOverlay (#9999)

@escaping is only valid on function parameters, not in tuple return types.
Closures in returned tuples are inherently escaping.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: make createdAt backward-compatible in IPC and HTTP session decoding (#10001)

Co-authored-by: Claude <noreply@anthropic.com>

* perf: batch guardian queries and add destination_conversation_id index in thread-candidates (#10002)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: normalize conversationId and escape titles in thread candidate handling (#10005)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: extract topBarView to resolve Swift type-checker timeout in MainWindowView (#10006)

The coreLayoutView computed property exceeded the Swift compiler's
type-checker complexity limit. Extract the top toolbar into a
separate topBarView property to reduce nesting depth.

Also fix latent .wakeWord enum case (should be .voice) that was
masked by the type-checker timeout.

Co-authored-by: Claude <noreply@anthropic.com>

* feat: update afternoon wake-up greeting to 'Wake up, my friend' (#10007)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: resolve Swift type-checker timeout in MainWindowView (#10008)

Extract topBarView from coreLayoutView to reduce expression complexity
that caused the compiler to bail. Also fix latent .wakeWord reference
(should be .voice) that was hidden by the type-checker timeout.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove duplicate topBarView declaration in MainWindowView (#10009)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat: remove name from all wake-up greetings so assistant is unnamed at hatch (#10010)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: add package.json fallback for APP_VERSION in bulletin sync (#10018)

* test: add atomic write failure path test for bulletin sync (#10019)

Simulates a write failure by making the temp directory read-only,
then verifies that original file content is preserved, no temp file
leftovers remain, and the function throws.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: align empty-template policy across template, tests, and docs (#10020)

Make the bundled UPDATES.md template comment-only so no bulletin is
materialized for releases without explicit update notes. Relax the
contract test to allow empty/comment-only templates. Update bulletin
tests to swap in a test template with real content during setup and
restore the original afterwards, plus add a new test verifying the
comment-only skip path.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: prevent clicking a thread from reordering it to the top (#10013)

When an LRU-evicted ViewModel is re-created on thread click, history
loading triggers the messages Combine publisher which calls
updateLastInteracted, bumping the thread to the top of the sidebar.
Skip updateLastInteracted while history is loading or not yet loaded.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: include assistant name in inbound voice pickup intro (#10022)

* fix: harden getDefaultRuleTemplates against partial config mocks (#10031)

Use optional chaining and runtime type guards in getDefaultRuleTemplates()
so partial config mocks (missing sandbox/skills branches) don't crash rule
generation. Adds a test covering the partial-config path.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: prevent empty thread from getting stuck on loading spinner (#10029)

trimForBackground() blindly reset isHistoryLoaded to false, even for
threads with no session. When switching back, loadHistoryIfNeeded bails
(no sessionId) but isHistoryLoaded stays false, leaving the UI stuck
on a loading spinner. Only reset when there's a session to reload from.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* Migrate browser relay clients to gateway ingress (#9938)

* Route browser relay and clients through gateway

* Exclude browser relay websocket from auth rate limiter

* Address bot feedback on readiness probe and token logging

* Gate browser relay ingress behind runtime proxy flag

* Harden browser relay upgrades to local/private peers

* fix: remove ok:false from slack deliver error response for consistency (#10034)

Co-authored-by: Claude <noreply@anthropic.com>

* docs: clarify warning field is POST-only in Slack config docs (#10035)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: isolate per-table failures in FTS reconciliation (#10039)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: move buffered cert reads inside TLS renewal fallback (#10040)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: reset global regex lastIndex before extractReleaseIds loop (#10041)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: preserve symlink targets in bulletin atomic write (#10042)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: preserve 0.0.0-dev fallback for local development (#10043)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: default sandbox to enabled when config is missing (#10044)

Co-authored-by: Claude <noreply@anthropic.com>

* docs: document token revocation failure semantics (#10045)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: wire up ConfigWatcher test assertions and remove unused locals (#10046)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: match custom socket check with resolveSocketPath semantics (#10047)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: use deterministic mock for bulletin write failure test (#10048)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: harden daemon startup lock and dev command cleanup (#10049)

Co-authored-by: Claude <noreply@anthropic.com>

* Fix browser relay host-spoof bypass in peer guard (#10037)

* fix: decouple bulletin tests from real template file (#10051)

Co-authored-by: Claude <noreply@anthropic.com>

* chore: skip approval prompt before final sweep in safe-blitz (#10056)

Co-authored-by: Claude <noreply@anthropic.com>

* feat: change default host_bash trust policy from ask to allow (#10053) (#10057)

Co-authored-by: Claude <noreply@anthropic.com>

* chore: remove defunct --auto flag from safe-blitz (#10069)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: re-throw aggregate FTS reconciliation failures (#10070)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: update sandbox default test to match new enabled-by-default behavior (#10072)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: handle dangling symlinks in bulletin atomic write (#10071)

Co-authored-by: Claude <noreply@anthropic.com>

* docs: fix daemon-restart token recovery semantics (#10073)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: avoid killing unrelated processes via stale PID files in dev startup (#10074)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: resolve actor isolation warnings in activation key observer (#10081)

Co-authored-by: Claude <noreply@anthropic.com>

* docs: remove --auto references and add deprecated alias in safe-blitz (#10082)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: mark live-streaming threads as history-loaded so assistant activity updates are not dropped (#10086)

Co-authored-…
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant