Commit Graph
4407 Commits
Author SHA1 Message Date
Waleed 220f8c8b10 feat(knowledge): add chunking strategies and regex strict boundaries (#4368)
* feat(knowledge): add chunking strategies and regex strict boundaries

- Add Token, Sentence, Recursive, and Regex chunkers with strategy selection in create-base modal
- Add opt-in strict boundaries mode for regex chunker so each match becomes its own chunk
- Add chunking strategies docs page with industry references

* fix(chunkers): strip capturing groups and validate strictBoundaries scope

- Convert capturing groups to non-capturing in regex chunker so split() doesn't surface delimiter text as spurious chunks
- Reject strictBoundaries in chunkingConfigSchema when strategy is not regex

* fix(chunkers): also strip named capture groups in regex patterns

Named groups (?<name>...) are still capturing groups so split() interleaves
their matched text. Convert them to non-capturing alongside plain ( groups.

* fix(chunkers): exclude lookbehind from named-group rewrite

Tighten NAMED_GROUP_PREFIX with negative lookahead so patterns like
(?<=<tag>) are not misidentified as named capture groups.
2026-04-30 18:33:52 -07:00
Waleed 124fe1796b improvement(toast): widen error toasts and bump line-clamp to 3 (#4370) 2026-04-30 17:48:22 -07:00
Theodore Li dafeaaaaa6 fix(ui): Add warning for organization-wide settings (#4366)
* fix(ui): Add warning for organization-wide settings

* Create emcn component

* Bump up text padding a tad

* apply emcn-design-review

* Address greptile comments
2026-04-30 19:09:52 -04:00
Waleed 46d6b1d332 fix(tasks): fix sidebar tasks skeleton hanging indefinitely (#4365)
* fix(tasks): fix sidebar tasks skeleton hanging indefinitely

Switch useTasks to skipToken + keepPreviousData and derive tasksLoading
from isLoading (isPending && isFetching) instead of data presence.
This matches the established useWorkflows pattern and prevents the
skeleton from hanging forever on query errors or when workspaceId
is temporarily absent.

* fix(tasks): show home UI for empty new tasks
2026-04-30 14:55:07 -07:00
Theodore LiandClaude Opus 4.7 49c12011dc feat(ui): update context menu (#4362)
* feat(ui): update context menu

* fix(user-input): clear mention state on submit

Programmatic plusMenuRef.close() sets the dropdown's internal open=false but
Radix doesn't fire onOpenChange for controlled changes, so handlePlusMenuClose
never ran and mentionRangeRef stayed truthy after submitting a message with
an active @mention. That caused the keydown handler to keep intercepting
ArrowUp/ArrowDown/Tab post-submit, breaking the "edit last queued message"
ArrowUp shortcut until the user typed again.

Clear mentionRangeRef and mentionQuery inline alongside the close() call.

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

* chore(ui): widen mention/plus dropdown to 420px

Long resource names were truncating in a 320px-wide menu. Bump to 420px and
cap with max-w on viewport so it can't overflow on small screens. Overrides
the emcn DropdownMenuContent's default max-w-[220px] via twMerge.

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

* chore(ui): trim mention/plus dropdown width to 360px

420px felt too wide; 360px gives long resource names enough room without
dominating the input area.

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

* tweak context menu widths

* address greptile review on PR #4362

- plus-menu-dropdown: drop folder type from the flat mention list (folders organize
  resources but aren't an insertable mention target — the nested rendering already
  excludes them).
- user-input: fold Enter into the mention-mode keydown guard so Enter confirms the
  highlighted resource instead of submitting the form. Falls through to the normal
  Enter-submit path when no match is highlighted (Tab keeps prior behavior).
- dropdown-menu wrapper: drop the misleading cast on the spread — runtime accepts
  onOpenAutoFocus regardless and the cast was hiding any future props added to
  DropdownMenuContentProps.
- plus-menu-dropdown: trim mention-mode + submenu widths to 300px (320px clipped on
  the narrower copilot panel).

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 17:33:22 -04:00
WaleedandClaude Opus 4.7 91092842e3 fix(settings): rename credentials to secrets, align role display (#4364)
- Rename credentials/ folder to secrets/ to match the page name
- Replace Badge with disabled Combobox for non-admin role display in secrets and integrations details views, full-width when no admin actions
- Reorder integrations credential row buttons so Details is rightmost
- Split credential-skeleton: page-level moves to secrets-skeleton, row-level moves to integrations/credential-skeleton
- Cleanup: useMemo→const where deps were trivial, ROLE_OPTIONS const, --text-success token

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 14:18:48 -07:00
Waleed d60747d4b7 feat(mothership): draft persistence, new task eager creation, doc preview fix, and loading polish (#4361)
* feat(mothership): draft persistence, new task eager creation, doc preview fix, and loading polish

- Add `useMothershipDraftsStore` (persist) to save and restore draft text, file
  attachments, and contexts per chat scope key; cleared on workspace reset
- Add `useCreateTask` mutation that eagerly POSTs a new chat, optimistically
  prepends it to the sidebar task list, and navigates to `/task/:id` — removes
  the `id:'new'` placeholder pattern entirely
- Fix docx/pptx/pdf preview: remove `!isDocFormat` guards in
  `file-preview-adapter` so preview sessions are created and streaming content
  is set during agent writes, suppressing intermediate compilation errors
- Replace `Loader2` (lucide) with `Loader` (emcn) across auth, chat, knowledge,
  logs, deploy, and tool-input components; add `Clipboard` icon to emcn
- Add `EditorContextMenu` to the Monaco file viewer
- Expand Monaco `SIM_DARK` and `SIM_LIGHT` token rules with string.link,
  delimiter, tag, attribute, and Markdown tokens (strong, emphasis, variable)

* fix(files): dispose onContextMenu listener and handle clipboard rejection

* fix(tasks): clear workspaceId:new draft after eager task creation

* fix(user-input): always update prevDefaultValueRef on defaultValue change

Prevents stale ref when defaultValue transitions through empty — if
defaultValue goes non-empty → empty → same non-empty value, the ref
was not updated on the empty transition, causing setValue to be skipped
on the subsequent change.

Also removes unnecessary useCallback wrapping from handleSendQueuedHead,
handleEditQueued, handleEditQueuedTail in MothershipChat — none have a
reference observer (UserInput stores them via refs, QueuedMessages is
not React.memo-wrapped).

* fix(files): improve light mode number token contrast to 4.8x

* fix(tasks): annotate raw fetch in createChat for boundary check

* fix(tasks): use requestJson with createMothershipChatContract instead of raw fetch

* fix(cleanup): remove cancelQueries from onSuccess, remove redundant Scissors className

* fix(tasks): guard handleNewTask against concurrent calls via ref lock

* fix(drafts): guard save effect against first-render race that wipes file-only drafts

On mount the save effect fired before the restore effect's setState calls
propagated, so fileAttachments and contexts were still empty. For a draft
with files but no text this caused isEmpty() to return true and setDraft to
delete the entry. Added isFirstSaveRef to skip the initial run; the restore
triggers a re-render that fires the save with the full, correct payload.

* fix(files): only show loading skeleton while fetching, relax mongo auth validation

- files.tsx: gate loading skeleton on isLoading so navigating to a missing
  file ID doesn't permanently show the skeleton after load completes
- database-tools.ts: remove refine() pairing username+password on mongo
  connection schema so either field can be provided independently

* revert(database-tools): restore mongo username+password paired validation
2026-04-30 13:45:59 -07:00
Vikhyath Mondreti 6b0de36238 chore(skills): update checklist for boundary e2e checklist (#4363)
* chore(skills): update checklist for boundary e2e checklist

* fix formatting
2026-04-30 13:12:09 -07:00
Vikhyath Mondreti b8959eb20d improvement(repo): zod based client-server boundary (#4355)
* improvement(repo): centralized zod contracts (#4336)

* improvement(repo): zod schema contracts

* type checks

* fix(notion): correctly register tool (#4337)

* fix func blokc

* more improvements

* fix tests

* type check

* remove v3 refs

* minor type improvements

* address comments

* update jira contract

* remove validateJsonBody

* improvement(repo): consolidation of boundary helpers + better unknown usage (#4352)

* improvement(repo): consolidation of boundary helpers + better unknown usage

* address comments

* improve file transfer error messaging

* fix docs listing schema drift

* fix inocrrect type casting

* address council comments

* remove prefix
2026-04-30 12:16:28 -07:00
Waleed 24de32b29a fix(secrets): invalidate env queries so dropdown updates without refresh (#4359)
* fix(secrets): invalidate env queries so dropdown updates without refresh

* improvement(secrets): use targeted workspaces key for env invalidation
2026-04-30 12:14:28 -07:00
Waleed 7377a10847 fix(custom-tool): include schema parameters in code wand prompt (#4360) 2026-04-30 12:14:07 -07:00
Waleed 07e7670d15 improvement(sidebar): remove unnecessary useCallback and useMemo wrappers (#4357)
* fix(sidebar): show skeleton on tasks error state, not 'New task' placeholder

* improvement(sidebar): remove unnecessary useCallback and useMemo wrappers

* fix(sidebar): use data-first pattern for tasks loading guard
2026-04-30 10:32:26 -07:00
WaleedandClaude Opus 4.7 d94f4c9943 feat(knowledge): add embedding model selection and Cohere reranker (#4349)
* feat(knowledge): add embedding model selection and Cohere reranker

* fix(knowledge): split reranker model constants into client-safe module

* fix(knowledge): bill rerank on every successful API call and fix MDX docs literal

* test(knowledge): align embedding tests with provider abstraction changes

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

* fix(knowledge): require explicit Azure deployment per OpenAI embedding model

Greptile P1: when AZURE_OPENAI_* was set, every OpenAI embedding model was
routed to the single KB_OPENAI_MODEL_NAME deployment. A KB created with
text-embedding-3-large would be embedded by whatever model that deployment
serves while billing tracked 3-large pricing — and chunks ingested via Azure
versus queried via real OpenAI would land in mismatched vector spaces.

Now require AZURE_OPENAI_DEPLOYMENT_TEXT_EMBEDDING_3_(SMALL|LARGE) per model.
Falls back to KB_OPENAI_MODEL_NAME only for text-embedding-3-small (legacy).
If no deployment is configured for the chosen model, route to direct OpenAI
instead of silently routing to the wrong deployment.

Also fix type predicate in search/route.ts to use KnowledgeBaseAccessResult
so the build passes.

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

* fix(knowledge): skip platform reranker billing for BYOK Cohere keys

Cursor bugbot found that resolveCohereKey discarded BYOK status, so the
search route always added platform rerankerCost even when the workspace
supplied its own Cohere key.

Now resolveCohereKey returns { apiKey, isBYOK } and rerank() returns
{ results, isBYOK }. The search route checks rerankIsBYOK before adding
rerankerCost or emitting the rerankerCost/rerankerSearchUnits fields,
mirroring how generateEmbeddings handles BYOK billing.

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

* fix(knowledge): match search tokenizer to embedding provider; remove dead var

Cursor bugbot:
- Token estimation was hardcoded to 'openai' for every embedding model.
  For gemini-embedding-001 the cost was computed against an OpenAI-tokenized
  count, producing wrong input.tokens.prompt and (slightly) wrong cost.
  Now derive the tokenizer provider from the embedding model's provider.
- rerankApplied was set but never read. Removed.

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

* fix(knowledge): match chunk tokenizer to KB embedding provider

Cursor bugbot: createChunk and updateChunk hardcoded the 'openai' tokenizer
when computing the stored tokenCount. For KBs using gemini-embedding-001 the
count was estimated with the wrong heuristic, leading to inaccurate stored
counts (and any billing derived from them). Now derive the tokenizer from
the KB's embedding model provider, matching the search route.

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

* refactor(knowledge): centralize tokenizer mapping on EmbeddingModelInfo

Add tokenizerProvider directly to EmbeddingModelInfo so callers read it
from the registry instead of reimplementing the gemini→google / openai→openai
map at each call site. Removes the local helper in chunks/service.ts and
the inline ternary in search/route.ts.

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

* refactor(knowledge): lock embedding model to KB_EMBEDDING_MODEL env var

Remove the user-facing model picker from the KB create modal and the
embeddingModel field from the create/update API schemas. The active model
is now selected server-side via KB_EMBEDDING_MODEL, which collapses Azure
routing to a single deployment (KB_OPENAI_MODEL_NAME) and drops the
per-model AZURE_OPENAI_DEPLOYMENT_TEXT_EMBEDDING_3_* env vars and
SUPPORTED_EMBEDDING_MODEL_IDS / UI-only label+description registry fields.

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

* fix(knowledge): use provider tokenizer for chunks and bound rerank indices

- documents/service.ts: replace ceil(len/4) heuristic with estimateTokenCount using the embedding model's tokenizerProvider so token counts match billing
- reranker.ts: filter Cohere rerank results to valid indices before mapping to defend against malformed responses
- utils.test.ts: add embeddingModel to kb fixture so getEmbeddingModelInfo resolves

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

* fix(knowledge): use .count from estimateTokenCount return value

estimateTokenCount returns a TokenEstimate object, not a number — access
.count so the integer token count is stored instead of an object.

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

* fix(knowledge): only enforce single embedding model when query is present

Tag-only searches don't generate a query embedding, so two KBs with
different embedding models can be filtered together. Gate the guard on
hasQuery so cross-model tag-only queries no longer 400.

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

* fix(knowledge): use getConfiguredEmbeddingModel in copilot KB creation

Copilot-created KBs were hardcoded to text-embedding-3-small, ignoring
KB_EMBEDDING_MODEL. This caused cross-KB searches mixing copilot- and
API-created KBs to hit the embedding-model-mismatch guard.

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

* fix(knowledge): make EMBEDDING_DIMENSIONS a literal type

CreateKnowledgeBaseData.embeddingDimension is typed as the literal 1536,
so EMBEDDING_DIMENSIONS needs `as const` to satisfy it after the copilot
path switched to passing the constant.

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

* fix(knowledge): use per-KB embedding model in v1 search route

The v1 search endpoint was passing undefined to generateSearchEmbedding,
which silently fell back to text-embedding-3-small. KBs created while
KB_EMBEDDING_MODEL=gemini-embedding-001 (or any non-default) would have
their queries embedded with the wrong model. Now resolves the model from
the KB rows like the internal route, with the same multi-model guard.

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

* chore(knowledge): polish embedding/reranker implementation

- Drop unused supportsCustomDimensions from EmbeddingModelInfo (every
  registered model supports it; OpenAI/Azure paths now always send
  dimensions: 1536).
- Type SUPPORTED_EMBEDDING_MODELS as Partial<Record<...>> so index lookups
  surface as possibly-undefined in the type system instead of relying on
  runtime null checks alone.
- Require AZURE_OPENAI_API_VERSION in the Azure routing gate. Missing
  api-version no longer slips through as ?api-version=undefined; it now
  falls back to direct OpenAI.
- Use the embedding provider's tokenizer (estimateTokenCount) for the
  Gemini fallback token estimate instead of len/4, so billing matches
  the model's tokenization.
- Drop unreachable 'text-embedding-3-small' fallback in the manual chunk
  upload route — accessCheck.knowledgeBase is non-null after the access
  guard.
- docs-chunker now reads getConfiguredEmbeddingModel() so Sim's docs
  ingestion respects KB_EMBEDDING_MODEL like the user-facing paths.
- Add v1 search route test covering per-KB model resolution and the
  cross-KB mixed-model rejection.

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

* fix(knowledge): resolve type errors and unhandled rejection in search routes

- Use accessCheck.knowledgeBase.embeddingModel directly in chunks response
- Narrow access-check predicate to KnowledgeBaseAccessResult in v1 search
- Move inaccessible-KB 404 check before query embedding promise creation

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

* fix(knowledge): pass Gemini API key via x-goog-api-key header

URLs end up in server access logs, proxy logs, and APM tools, so embedding
the key as a query param risks accidental exposure. Google explicitly
recommends the header form for the Gemini REST API.

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

* fix(knowledge): default Azure deployment name to embedding model name

Restore the prior fallback so existing Azure deployments — which conventionally
name the deployment after the model — continue to route through Azure when
KB_OPENAI_MODEL_NAME is unset. Before this fix, those deployments silently fell
through to direct OpenAI.

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

* fix(knowledge): cap Gemini batches at 100 items, add singular GEMINI_API_KEY fallback

- Gemini's batchEmbedContents API rejects requests with more than 100
  items. The token-based batcher could pack hundreds of short chunks
  into a single request, causing 400s. Add maxItemsPerRequest on
  ResolvedProvider and split token batches further when set.
- Mirror resolveOpenAIKey by accepting GEMINI_API_KEY (singular) as a
  fallback before requiring the rotating GEMINI_API_KEY_1/2/3 keys.

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

* fix(knowledge): prefer singular Cohere key before rotation

Match resolveOpenAIKey/resolveGeminiKey order: check the singular
COHERE_API_KEY before falling back to rotating keys.

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 01:45:56 -07:00
Waleed cb8ea3a870 fix(fork): clear task selection before navigating to fork (#4356) 2026-04-29 23:39:02 -07:00
Waleed cb43b596b9 feat(fork): optimistic sidebar entry + Fork | prefix for forked tasks (#4353)
* feat(fork): optimistic sidebar entry + rename to 'Fork | Chat Name'

Adds the forked task to the sidebar immediately by writing an optimistic
entry into the task list cache in onSuccess, before the background refetch
arrives. Without this, the sidebar showed no new task until the invalidation
refetch completed after navigation.

Also moves the Fork prefix to the front of the name ('Fork | Chat Name')
so it is immediately scannable in the sidebar.

* fix(fork): cancel in-flight queries before optimistic sidebar write
2026-04-29 22:39:59 -07:00
Waleed 2bb5c91554 improvement(kb-selector): add search to knowledge base selector subblock (#4351) 2026-04-29 19:59:01 -07:00
Waleed 541c1b5846 fix(fork): scope task list invalidation to current workspace (#4350)
* fix(fork): scope task list invalidation to current workspace

* fix(sidebar): remove mutation objects from useCallback deps

Mutation objects are not referentially stable in TanStack Query v5 — only
.mutate() and .mutateAsync() are stable. Removed deleteTaskMutation,
deleteTasksMutation, markTaskReadMutation, and markTaskUnreadMutation from
their respective useCallback dependency arrays to avoid unnecessary
recreations on every mutation state update.
2026-04-29 19:35:41 -07:00
WaleedandClaude Opus 4.7 0c69302369 fix(integrations): harden jira, jsm, ashby, google drive, slack, confluence, notion (#4345)
* fix(integrations): validate and harden jira, jsm, ashby, google drive, slack, confluence, notion

Audit and fix contract drift, input validation, and error handling across integrations:

- Jira: NaN guards on worklog seconds, JSON.parse try/catch on internal API responses,
  domain normalization (strip leading https://), JQL injection prevention via project
  key validation, ADF helper consolidation, /search/jql nextPageToken pagination,
  defensive .trim() on ID path params, encodeURIComponent on watcher account IDs,
  resolveAssigneeAccountId helper, parent-as-object wrapping, summary fallback,
  add read-bulk operation. Restored total field (always null) to preserve contract.
- JSM: customer/organization route validation
- Ashby: types and tool output cleanup across all 30+ tools
- Google Drive: tighter response handling across read/write/share tools
- Slack: types and tool fixes (canvas, reactions, messaging, members)
- Confluence: update tool and types
- Docs: regenerated mdx for all touched integrations

* fix(ashby): add subblock migrations for removed expand form definition fields

* fix(slack): restore canvas_id fallback to data.id for backwards compat

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

* fix(jsm): explicit 400 when deprecated `emails` param is sent

Address greptile review on PR #4345: instead of silently dropping
`emails` and falling through to list-customers, return a 400 telling
the caller to use `accountIds`.

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

* fix(google_drive): include HTTP status in fallback error messages

Address greptile review on PR #4345: when Google Drive returns a non-JSON
error body, surface the response status/statusText so failures are
diagnosable instead of falling through to a generic message.

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

* fix(ashby): drop stray websiteUrl→website remap for update_candidate

The update_candidate tool reads params.websiteUrl directly; mapping it
to result.website added a confusing dead field. The websiteUrl subBlock
auto-passes through with the matching name.

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

* fix(google_drive): rename canonical params to avoid subBlock ID clash

`mimeType`, `query`, and `pageSize` canonical IDs collided with existing
subBlock IDs in the same block (failing the canonical-param validation
test). Drop the canonicalParamId from search/get_content single-input
fields and route them to tool params explicitly in tools.config.params.

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

* fix(ashby): remove filterCandidateId from removed-subblock migrations

The candidate-id filter was reintroduced as a valid Ashby subBlock, but
the migration map still rewrote it to _removed_filterCandidateId on every
workflow load, silently breaking the field. Drop the entry so user values
persist.

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

* fix(ashby): restore documented response fields dropped during refactor

Restore three fields that exist in Ashby's API responses but were dropped
during the recent refactor: applicationLimitCalloutHtml on /jobPosting.info,
compensation on /job.info (and add the `compensation` expand), and managerId
on /user.list.

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

* correctness

* updated types

* fix(ashby): gate operation-specific param mappings to prevent stale overwrites

Multiple subBlocks share the same target tool param (createdAt is set by
appCreatedAt/candidateCreatedAt/noteCreatedAt; candidateId by appCandidateId/
filterCandidateId). Because subBlock values persist across operation switches,
a stale value from a prior operation could silently overwrite the correct one.
Guard each mapping with an explicit operation check.

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

* fix(ashby): gate offerApplicationId mapping by operation

Same shared-target hazard as the prior fix: offerApplicationId maps to
result.applicationId without an operation guard, so a stale value from
list_offers could overwrite the active applicationId on get_application,
change_application_stage, or list_interviews.

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

* fix(ashby): include list_locations in includeArchived condition

Ashby's /location.list accepts includeArchived per the API docs, and the
docs page already documents the toggle for list_locations. Add the missing
operation value so the toggle renders.

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

* fix(jira): forward explicit notifyUsers=true query param on issue update

Block now distinguishes true/false/undefined for notifyUsers, but the route
collapsed true and undefined into a no-param request. Forward the explicit
true intent so it survives any future API default change or proxy override.

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

* fix(jira): quote project key in JQL to defend against injection

* fix(jira): quote project key in bulk_read JQL for defense in depth

The alphanumeric regex check above already blocks injection, but quoting
the project key matches the pattern used elsewhere (issues/route.ts) and
hardens the path against future regex changes.

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-29 19:08:23 -07:00
Waleed 36742740aa fix(cleanup): batch orphaned snapshot deletes to avoid slow-query spike (#4348)
* fix(cleanup): batch orphaned snapshot deletes to avoid slow-query spike

* fix(cleanup): recheck orphan condition in delete to close TOCTOU gap
2026-04-29 18:02:46 -07:00
Waleed 60816702f5 fix(files): use incremental applyEdits to prevent streaming flicker in Monaco editor (#4347)
* fix(files): use incremental applyEdits to prevent streaming flicker in Monaco editor

* fix(files): use actual model value for additive applyEdits guard

* fix(files): use pushEditOperations instead of applyEdits to align with VS Code streaming pattern

* fix(files): revert to applyEdits to avoid polluting undo stack during streaming
2026-04-29 17:41:02 -07:00
Waleed a9d4e2eca8 fix(trace): normalize keyed tool names and show credits in trace view (#4344)
* fix(trace): normalize keyed tool names and show credits in trace view

- Export normalizeToolId from tools/index.ts so trace-view can reuse it
- Strip resource-id suffixes (knowledge_search_<uuid>, workflow_executor_<uuid>, table_*_<id>) from tool span names at display time so icons resolve and names are readable
- Replace raw dollar formatting with credits in trace header and agent detail panel

* fix(trace): fix sub-credit display, locale, and pluralization in formatCostAmount

* fix(trace): extract normalizeToolId to client-safe module, consolidate log-details formatting utils

* refactor(trace): consolidate shared span utilities into log-details/utils

* fix(tools): import normalizeToolId for internal use after extracting to normalize.ts

* style(trace-spans): replace inline transform style with Tailwind rotate-180 on disclosure chevron

* fix(trace-spans): show credits instead of dollars for cost display

* refactor(trace): move formatCostAmount and getDisplayName to shared utils, normalize tool names in trace-spans

* fix(trace-spans): simplify formatCostSummary to show total credits only
2026-04-29 15:34:02 -07:00
Waleed 1a321c5d5f feat(fork): fork chat from any assistant message (#4343)
* feat(fork): fork chat from any assistant message

* fix(fork): toast on failure, disabled opacity, copy previewYaml/planArtifact/config

* fix(fork): type guard for mothership-only, prevent title accumulation on re-fork

* fix(fork): register task_forked PostHog event type
2026-04-29 12:51:17 -07:00
Waleed 7d8ec24768 feat(firecrawl): add parse operation and revert short-input selection style (#4340)
* feat(firecrawl): add parse operation and revert short-input selection style

* chore(firecrawl): regenerate docs and integrations data for parse

* fix(firecrawl): forward firecrawl error body in parse route response

* fix(firecrawl): add pricing config to parse tool hosting
2026-04-29 12:04:12 -07:00
Theodore Li 8d042f7d20 fix(copilot): fix new task error (#4341)
* fix(copilot): fix new task error

* Keep recent copilot chat open on refresh
2026-04-29 14:39:24 -04:00
Waleed 94f5411a2b fix(files): streaming preview invariant + OOXML style extraction (#4335)
* fix(files): suppress transient streaming preview errors for docx and pptx

* feat(files): add OOXML style extraction for uploaded docx/pptx files

New GET /api/workspaces/[id]/files/[fileId]/style endpoint + VFS read
path files/by-id/{id}/style that returns a compact JSON style summary
from an uploaded binary .docx or .pptx: theme name, 12-slot color
palette, major/minor font pair, and key named styles (Normal, H1-H3,
Title). Logic lives in a shared lib/copilot/vfs/document-style.ts so
both the REST API and the VFS read handler reuse the same parsing code.

* chore(files): polish style extraction — type narrowing + empty-styles guard

Explicit 'docx' | 'pptx' type annotation after the extension guard in
both route.ts and workspace-vfs.ts so TypeScript sees the narrowed type
rather than string. Only set summary.styles when the parsed array is
non-empty so the JSON response doesn't include "styles": []. Remove
redundant inline WHAT-comments from parseColorSlot.

* fix(files): tighten streaming preview invariant and component consistency

- Apply structural invariant to PDF streaming path: never surface errors
  while streamingContent is defined; only log at info level
- Remove redundant setRenderError(null) from DOCX streaming effect — the
  gate at the display layer already suppresses errors during streaming
- Wrap PptxPreview in memo for consistency with DocxPreview
- Add key={file.id} to PptxPreview mount site (was missing, DocxPreview
  had it) so the component resets when the viewed file changes
- Fix --text-body → --text-primary across PreviewError, UnsupportedPreview,
  and MermaidDiagram error label; --text-body is not a valid EMCN token

* fix(files): remove setRenderError(null) from PPTX and PDF streaming paths

* feat(files): add compiled-check endpoint and VFS path for binary document self-verification

* fix(files): remove dead renderError state from IframePreview

* refactor(files): hoist BINARY_DOC_TASKS to module scope in compiled-check route and VFS handler

* fix(files): deduplicate BINARY_DOC_TASKS and add size guard to VFS compiled-check
2026-04-29 09:05:53 -07:00
Waleed 52c93d414b improvement(docs): soften video hover opacity (#4339) 2026-04-29 08:43:15 -07:00
Vikhyath Mondreti 61342e2567 fix(notion): correctly register tool (#4337) 2026-04-28 20:41:40 -07:00
Theodore LiandClaude Opus 4.7 ed7786d7fd fix(ui): adjust docx and code rendering (#4334)
* fix(ui): adjust docx and code rendering

* fix(ui): address PR feedback on docx fit and Monaco type cast

Use computed padding from `.docx-wrapper` instead of a hardcoded 60px
so the fit calculation survives docx-preview library changes. Replace
the inline `import()` cast for ShowLightbulbIconMode with a top-level
type import.

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

* fix lint

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 22:52:11 -04:00
Waleed e2ded1643c fix(snapshot): stop markdown preview auto-scroll during patch streams + snapshot styling (#4333)
* fix(files): stop markdown preview auto-scrolling during copilot patch streams

fix(snapshot): use surface-4 background for input/output code blocks in light mode

* fix(files): separate auto-scroll gate from streaming render mode in MarkdownPreview
2026-04-28 18:37:15 -07:00
Waleed 7b55e605be improvement(sap_s4hana): use MERGE for OData v2 updates and enlarge icon (#4332) 2026-04-28 18:21:33 -07:00
Waleed 7f12a139b5 fix(knowledge): skip sync and document processing when KB is deleted (#4327)
* fix(knowledge): skip sync and document processing when KB is deleted

* fix(knowledge): mark connector errored and document failed when KB is deleted

* fix(knowledge): mark connector errored in dispatchSync when KB is deleted
2026-04-28 17:41:23 -07:00
WaleedandClaude Opus 4.7 9a4cd95182 feat(logs): trace span tree rewrite with resizable split, provider icons, and execution improvements (#4292)
* improvement(trace-spans): rewrite trace span pipeline with per-iteration enrichment

Unify tool calls under span.children, capture dual-clock timing, and
surface per-iteration model content (assistant text, thinking, tool
calls, finish reason, tokens, cost, ttft, provider, errors) across all
12 LLM providers. UI renders the new fields on model child spans; old
logs degrade gracefully since every field is optional.

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

* improvement(logs): add Trace tab with two-pane tree+detail view

- Wrap log-details drawer in Overview | Trace tabs; Overview unchanged
- New TraceView with hierarchical tree on the left and detail pane on the right
- Keyboard nav, span filter, expand/collapse all
- Bump min drawer width 400->600 and clamp persisted widths on rehydrate

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

* feat(logs): retry failed runs + show workflow input in detail

Brings PR #4181 inline: persists workflowInput on successful runs,
adds useRetryExecution mutation (streaming read-one-chunk-and-cancel),
Retry entrypoints in the row context menu and the detail sidebar, and
extractRetryInput with fallback to starter block state for older logs.
Also surfaces the captured input in a new "Workflow Input" section
above Workflow Output in the detail Overview tab, guarded so older
logs without the field don't render an empty block.

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

* fix(ui): use inverted popover scheme for usage-control popovers

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

* fix(logs): trace view chevron padding, section state leak, and tab-scoped keyboard nav

- Pad tree rows from panel edge so the root chevron isn't visually clipped.
- Key DetailCodeSection by label so collapse state belongs to the section
  purpose, preventing isOpen from leaking across span changes when positional
  slots happened to align.
- Ignore log-to-log arrow-key nav while the Trace tab is active so TraceView
  owns span navigation; filter inputs keep native caret movement.

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

* fix(logs): align trace tree rows to 14px content grid

Chevron at depth 0 and the timeline bars now sit on the same 14px left/right
grid as the trace view's header strip and the rest of the log details panel,
removing the stagger where bars extended further left than chevrons and the
chevron appeared cramped against the panel edge.

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

* fix(logs): restore scroll in log-details panel

Overview tab's scroll container (SModalTabsContent) was wrapped around a
non-overflow inner div that held the scrollAreaRef, so the scroll-reset on
log change targeted a non-scrolling element. Collapse the wrapper into the
Tabs.Content element itself and move the ref there. Add min-h-0 to the
Trace detail pane wrapper so its scrolling child can shrink inside the
horizontal-flex row.

* fix(logs): hide inactive Overview tab panel

Tailwind's `.flex` utility overrides the UA `[hidden]` rule, so applying
`flex` to SModalTabsContent caused the inactive Overview panel to still
participate in the Tabs flex column and push the Trace view down. Keep
SModalTabsContent as a plain overflow container (no `flex` class) with
the scroll ref on it, and restore the inner flex-col wrapper for the
Overview content so it still stacks with gap spacing.

* fix(logs): trace view padding, section cutoff, keyboard visibility

- Tree pane now has top padding so the first row has breathing room
  under the header strip instead of sitting flush against the border.
- DetailCodeSection dropped its wrapper `overflow-hidden`. Per CSS, a
  flex item with `overflow: hidden` resolves `min-height: auto` to `0`,
  so when Input and Output were both expanded the flex algorithm
  shrank each section below its content, cutting off rows. Without the
  clip, sections size to content and the surrounding pane's
  `overflow-y-auto` takes over.
- Selected span row now scrolls into view on selection change, so
  arrow-key navigation always keeps the active row visible in the
  tree pane.

* fix(logs): inline Workflow State row and lift search dropdown z-index

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

* fix(logs): use emcn Button for View Snapshot action

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

* minor improvements

* fix(logs): trace view resizable split, bar visibility, provider icons, cleanup

- Resizable tree/detail split in trace view (default 360px, drag to resize)
- Resizable right panel in preview snapshot (280–600px)
- Fix Gantt bar invisibility for late-run spans (clamp offsetPct to 100-MIN_BAR_PCT)
- Propagate model+provider to child model spans in span-factory for correct icons
- Fix icon contrast on light provider backgrounds (luminance-based color class)
- Replace custom status badges with emcn Badge component
- Lighten jump-to-error button to ghost variant
- Remove double X button in modal snapshot (showBlockCloseButton prop)
- Fix emcn subpath imports → barrel in trace-view, log-details, execution-snapshot
- Fix hover: → hover-hover: on resize handles
- Add body style cleanup on resize unmount
- Fix React Query key factory naming (stats/stat convention)
- Remove unnecessary useCallback/useMemo in preview and execution-snapshot

* fix(ui): scroll guard, credentials UX, design token fixes, input padding

- logs: only scroll-into-view on keyboard nav, not on click selection
- resource: stable scrollbar gutter, wider first column
- credentials: toast success/error feedback, remove useMemo for personalEnvData,
  allow editing conflict rows, fix disabled state visibility, use --text-error token
- integrations: use --text-error token for error state
- input: increase right padding (px-2 → pl-2 pr-3)

* chore(skills): add /ship command to claude, cursor, and agents

* fix(input): add scroll-pr-1 to keep caret visible when text overflows

* fix(logs): address PR review — iteration name guard, cost race, mothership retry

* improvement(logs): cleanup pass — remove anti-patterns, fix design tokens, simplify state

* fix(trace-spans): extend final model segment by position not by stale constant name

* fix(modal): restore sidebar-width padding on non-workflow pages

* fix(secrets): eliminate slow save by parallelizing DB ops and fixing stuck button

Sequential per-variable, per-workspace DB round-trips in syncPersonalEnvCredentialsForUser caused O(W×K) latency (800–1600ms for 10 workspaces). Replaced with parallel workspace processing and batched upserts. Also parallelized secret decryption in the GET handler.

On the client, removed the changeToken bug that left the Save button permanently disabled after a failed save, split the shared hasSavedRef into two independent flags to eliminate ordering races, and moved ref updates to after mutation success so optimistic state can never get stuck.

* updated sap block

* fix(sap): remove slash from S4HANA name, set white bgColor, regenerate docs

* fix(logs): prevent log-row arrow navigation when trace tab is active

* fix(logs): aggregate cost onto workflow root span; stabilize onActiveTabChange callback

* improvement(logs): fix Gantt time bounds to walk full span tree; cleanup effects, memos, callbacks, React Query mutations

* fix(logs): reset detail panel tab to overview on log switch

* chore(logs): remove extraneous comments

* fix(logs): restore useEffect for async setActiveWorkflow and useMemo on rowProps

- resource-content.tsx: revert render-time setActiveWorkflow call to useEffect; the store action is async and performs network ops, calling it during render violates React purity
- logs-list.tsx: restore useMemo on rowProps to prevent virtualized list rows from re-rendering on every parent render

* fix(queries): forward AbortSignal in mothership-admin query functions

All queryFn callbacks must forward signal for request cancellation per project React Query standards.

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 17:34:49 -07:00
Waleed ed206ed7d8 feat(table): expose position parameter for row insertion via copilot (#4326)
* feat(table): expose position parameter for row insertion via copilot

* fix(table): validate positions length matches rows before service call

* chore(table): regenerate tool types from updated copilot contract

* fix(table): validate no duplicate positions in batch_insert_rows

* chore(table): regenerate tool types with integer position schema
2026-04-28 17:30:29 -07:00
Waleed 3784f5465a fix(table): return 400 instead of 500 on empty batch insert (#4329)
The POST /api/table/[tableId]/rows handler called handleBatchInsert
without await, so ZodError rejections (e.g. empty rows array) bypassed
the route's try/catch and were caught by withRouteHandler as an
unhandled error, returning a generic 500 "Internal server error". Add
await so validation errors surface as 400s with the actual message.
2026-04-28 17:26:14 -07:00
Waleed 9ac73bb4b8 fix(billing): gate org billing query to invite modal open state and allow GA doubleclick in CSP (#4328)
* fix(billing): gate org billing query to invite modal open state and allow GA doubleclick in CSP

* fix(csp): drop redundant stats.g.doubleclick.net entry
2026-04-28 17:17:01 -07:00
Theodore Li 18a559bfb5 fix(copilot): use different chats for different workflows (#4324)
* fix(copilot): use different chats for different workflows

* remove use effect

* trim comments
2026-04-28 19:19:01 -04:00
Theodore Li feeb0a8920 fix(workflow): throw 4xx on variable resolution failures (#4325)
* fix(workflow): throw 4xx on variable resolution failures

* Switch spread order
2026-04-28 18:49:51 -04:00
WaleedandClaude Opus 4.7 ceb7d20aed fix(docker): use full bun.lock + bump deprecated GHA actions (#4323)
* fix(docker): use full bun.lock and copy it into builder

The staging build for app.Dockerfile (commit dc2022995, PR #4322) is
failing in two ways after switching to turbo prune:

1. turbo 2.9.6's pruned bun.lock is malformed for bun 1.3.x:
     error: Failed to resolve prod dependency 'wrap-ansi' for package
     'log-update' at bun.lock:2688:5
   Bun ignores it and falls back to a fresh resolve (~7m install).

2. Next.js 16.1.6's Turbopack production build can't infer the workspace
   root because /app/bun.lock doesn't exist in the builder stage:
     Error: We couldn't find the Next.js package (next/package.json)
     from the project directory: /app/apps/sim
   This blocks the build entirely.

Fix:
- deps stage: use the full bun.lock from /app/bun.lock (the original
  lockfile after `COPY . .` in pruner) instead of the broken
  /app/out/bun.lock that turbo prune emits.
- builder stage: also copy the full bun.lock to /app/bun.lock so
  Turbopack and turborepo can detect the workspace root.

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

* chore(ci): bump deprecated Node.js 20 actions to Node.js 24 versions

GitHub Actions runners emit deprecation warnings for actions still on the
Node.js 20 runtime. Node.js 20 will be force-upgraded by GitHub on
2026-06-02 and removed on 2026-09-16.

Bumps to the latest stable major versions, all of which use Node.js 24:

- actions/cache: v4 -> v5
- actions/setup-node: v4 -> v6
- aws-actions/configure-aws-credentials: v4 -> v6
- docker/login-action: v3 -> v4

All require GHA runner v2.327.1+ which Blacksmith already runs.

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 13:47:36 -07:00
Waleed 6a7d5ae828 feat(files): extract PDF viewer behind SSR boundary and polish file preview (#4316)
* feat(files): extract PDF viewer behind SSR boundary and polish file preview

## Core architectural fix

Move all react-pdf / pdfjs-dist code into a new pdf-viewer.tsx module and
import it exclusively via next/dynamic({ ssr: false }). pdfjs-dist v5
references DOMMatrix at module evaluation time, which crashed SSR. The
previous workaround (a DOMMatrix polyfill in instrumentation.ts) is removed
in favour of this proper hard module boundary.

## PDF viewer improvements

- Cursor-anchored zoom: Ctrl/⌘+wheel and trackpad-pinch now zoom toward the
  cursor instead of the top-left corner. Toolbar ± buttons anchor to the
  viewport centre. Uses the canonical scroll-adjust formula used by map and
  canvas viewers.
- Horizontal scroll: dropping flex-col from the scroll container lets the
  zoomed pages wrapper overflow naturally and produces a horizontal scrollbar
  at zoom > 1×.
- Loading skeleton: replaced the conditional inline skeleton with an
  absolute inset-0 overlay so it fills the scroll container correctly in all
  layout contexts.
- Shadow tokens: fixed shadow-[var(--shadow-medium)] and
  shadow-[var(--shadow-card)] to use the Tailwind utility classes
  shadow-medium and shadow-card directly.

## File viewer cleanup

- data-table.tsx: wrap setInputRef in useCallback([]) so the ref callback
  has a stable identity across renders. Previously the inline function got a
  new identity on every keystroke (because editValue state changed), causing
  React to teardown/remount the ref and re-run node.select() on every
  character typed.
- preview-panel.tsx: keep useMemo on ctxValue passed to Context.Provider —
  Context uses Object.is, so a new object every render causes unnecessary
  consumer re-renders.
- resource-content.tsx: remove unnecessary useCallback/useMemo wrappers on
  handlers and derived values that have no memoization observers.

## API route

- Wrap content route with withRouteHandler for automatic request-ID tracking
  via AsyncLocalStorage; remove manual generateRequestId() calls.
- Add resourceName to audit record; add encoding param support (base64 /
  utf-8).

## Query hooks

- Include key (storage object key) in both useWorkspaceFileContent and
  useWorkspaceFileBinary query key tuples so the cache is correctly busted
  when a file is re-uploaded with a new storage key.

## Other

- Add Suspense boundaries to files/page.tsx and files/[fileId]/page.tsx
  (required for useSearchParams inside the Files component).
- Add mmd to SUPPORTED_CODE_EXTENSIONS (Mermaid diagrams).
- Add https: to CSP img-src.
- Remove ==== separator comments from lib/copilot/constants.ts.
- New dependencies: pdfjs-dist 5.4.296, mermaid 11.14.0,
  monaco-editor 0.55.1, @monaco-editor/react 4.7.0.

* fix(files): replace instanceof Error checks with toError() and fix skeleton tokens

- Use toError() from @sim/utils/errors across all catch blocks in
  file-viewer.tsx, preview-panel.tsx, and route.ts instead of the
  prohibited `err instanceof Error ? err.message : fallback` pattern
- Fix loading skeleton in files.tsx: bg-white → bg-[var(--surface-2)]
  and shadow-[var(--shadow-medium)] → shadow-medium

* fix(files): address PR review findings

- csp.ts: revert bare https: from img-src — it defeats the existing
  domain allowlist and opens info-leakage vectors
- files/page.tsx + files/[fileId]/page.tsx: add explicit fallback={null}
  to <Suspense> to make intent clear (React defaults to null, but
  omitting it looks like an oversight)
- preview-panel.tsx: restore pre passthrough in STATIC_MARKDOWN_COMPONENTS
  so Streamdown's wrapping <pre> doesn't nest inside the custom code
  block <div>, which produced invalid HTML and broken styling
- file-viewer.tsx: add 'webm' to VIDEO_PREVIEWABLE_EXTENSIONS to match
  'video/webm' in VIDEO_PREVIEWABLE_MIME_TYPES

* chore(files): revert accidental pptxgenjs.cjs re-minification

The bundle was regenerated non-deterministically during development (same
pptxgenjs 4.0.1, different variable names in minifier output). No functional
change — restore the prior version to keep the diff clean.

* fix(files): fix Monaco stale closure, XLSX Ctrl+S data loss, and async workbook mutation

Three bugs from Cursor Bugbot follow-up review:

1. Stale closure in handleEditorMount (Medium): useCallback([], []) captured
   content='' at first render. When Monaco mounts after content loads (e.g.
   switching from preview to editor mode), lastSyncedContentRef was never
   initialized and external content changes stopped syncing. Fixed by keeping
   a contentRef updated on every render and reading it inside handleEditorMount.

2. XLSX Ctrl+S discards active cell edit (Medium): handleSave read from
   workbookRef.current before DataTable's in-progress editValue was committed.
   Fixed by exposing commitEdit() from DataTable via useImperativeHandle
   (using an always-current editStateRef so the handle stays stable) and
   calling it at the top of handleSave.

3. Async workbook mutation fragility (Low): handleCellChange / handleHeaderChange
   updated the workbook inside import('xlsx').then(), creating microtask-order
   coupling with handleSave. Fixed by caching the xlsx module in xlsxModuleRef
   on first parse and using it synchronously in both handlers.

* refactor(files): cleanup anti-patterns across file viewer components

Six-pass cleanup over the file-viewer directory:

Effects (you-might-not-need-an-effect):
- AudioPreview, VideoPreview: replace reset useEffect with key={file.id} so
  the component remounts on file change — React's canonical solution
- DocxPreview: same key-prop fix; removes a 5-setState reset effect that was
  also clearing containerRef.current.innerHTML unnecessarily

Callbacks (you-might-not-need-a-callback):
- handleEditorMount, handleEditorChange: remove useCallback — MonacoEditor is
  dynamic(), not React.memo, so reference stability has no observer
- markSavedContent: remove useCallback — called only through an onSaveRef,
  never directly observed
- DataTable.setInputRef: remove useCallback — callback refs on native elements
  are called regardless of reference identity

Design tokens (emcn-design-review):
- VideoPreview: bg-black → bg-[var(--surface-inverted)]
- HtmlPreview iframe: bg-white → bg-[var(--surface-2)]

useMemo, useState, and react-query passes found no issues.

* improvement(files): replace stock Monaco theme with Sim design system theme

Define sim-dark and sim-light Monaco themes using Sim's exact design tokens
instead of the default vs/vs-dark which looked identical to stock VSCode.

Chrome changes (both themes):
- Background, gutter use --bg (not VSCode's near-black / pure-white defaults)
- Line numbers use --text-muted instead of VSCode gray
- Cursor switches to --brand-secondary (#33b4ff)
- Selection highlight is brand blue at 15% opacity
- Scrollbar shadow removed, track uses surface tokens
- Bracket match, word highlight, find match all keyed to brand blue
- Suggestion/hover widgets use --surface-2 / --border tokens
- All hardcoded shadows removed (scrollbar.shadow = transparent)

Syntax token changes (inherit: true — base handles unlisted tokens):
- Comments: muted gray + italic (vs VSCode's bright green)
- Strings: #3ab872 dark / #16825d light (vs VSCode orange-red)
- Numbers: warm amber / warm orange (both readable on their backgrounds)
- Keywords: #33b4ff dark / #0078d4 light (brand blue family)
- Types: complementary blue-gray / purple

* fix(files): bump light theme comment color to #888888 for WCAG contrast

* fix(files): fix dark mode comment contrast #4a4a4a → #606060 (~1.9:1 → ~2.9:1)

* improvement(files): cursor to default color, video background to surface-1

- Monaco cursor: #33b4ff (brand blue) → #e6e6e6 dark / #1a1a1a light
  (text cursor should be neutral, not loud)
- VideoPreview background: var(--surface-inverted) → var(--surface-1)
  (consistent with PDF viewer, fits workspace context over cinema-black)

* fix(files): stabilize setInputRef callback and guard against double-commit in DataTable

Wrap setInputRef in useCallback([], []) so React doesn't tear down and
re-mount the input ref on every keystroke. Without stable identity, every
editValue state change caused node.focus()/node.select() to fire, resetting
the cursor selection to "select all" on each character typed.

Add isCommittedRef to guard both the imperative commitEdit handle and the
inline commitEdit (called by onBlur) against double-application. The ref is
cleared in startEdit and set to true on the first commit, so a concurrent
onBlur cannot re-apply the same edit.

* fix(files): preserve scroll position during Mothership streaming edits

Two fixes to the Monaco auto-scroll logic:

1. At streaming start, initialize textareaStuckRef from the editor's actual
   scroll position (isAtBottom check) instead of unconditionally setting true.
   Previously every streaming session jumped the viewport to the last line on
   the very first content update, even when the user was reading mid-file.

2. Replace the wheel-only DOM listener with editor.onDidScrollChange(), the
   proper Monaco API. This covers trackpad, scrollbar drag, and keyboard scroll
   — not just mouse wheel. As a bonus, scrolling back to the bottom during
   streaming now re-engages follow mode (matching iTerm2/xterm.js behavior).

3. Save and restore view state around model.setValue() during streaming when
   the user has scrolled away from the bottom. This prevents Monaco from
   resetting the viewport on each content replacement. When the user is at
   the bottom, view state is not saved so Effect 3 can scroll to the new bottom.

* fix(files): fix two scroll logic bugs introduced in previous streaming scroll fix

The prior fix introduced a regression for the "user was at bottom" case and
a false-disengagement bug from programmatic scroll events.

Bug 1 — Effect ordering: all three effects fire on the same render when
isStreamInteractionLocked flips true. Effect 2 called isAtBottom() AFTER
Effect 1 had already called model.setValue(), which grew scrollHeight. The
old "at bottom" scroll position was now 200px short of the new bottom, so
isAtBottom() returned false, textareaStuckRef was set false, and Effect 3
never called revealLine. Users at the bottom stopped following the stream.

Fix: measure isAtBottom() in Effect 1 BEFORE setValue, while scrollHeight
is still accurate. Set textareaStuckRef = true only (never false here).
Effect 2 no longer initializes the ref — only the listener disengages it.

Bug 2 — onDidScrollChange fires during model.setValue: Monaco fires
onDidScrollChange when scroll dimensions change, including when setValue
grows the document. This caused the listener to disengage auto-scroll on
every content update even with no user interaction.

Fix: add suppressScrollListenerRef, set true before setValue/restoreViewState
and false after. The listener exits early when suppressed, so only genuine
user scroll events (wheel, trackpad, keyboard, scrollbar) can disengage.

Both refs moved to the component's ref block for conventional placement.

* chore(files): remove extraneous comments from file viewer and data table

* refactor(files): split 2281-line file-viewer.tsx into focused modules

TextEditor, DocxPreview, PptxPreview, XlsxPreview, ImagePreview each moved
to their own files. Shared utilities (PreviewError, resolvePreviewError,
shouldSuppressStreamingDocumentError, PDF_PAGE_SKELETON) extracted to
preview-shared.tsx. file-viewer.tsx is now the orchestrator + MIME constants
+ small stateless previews (~495 lines).

* fix(files): remove unnecessary TextEditorProps export

* refactor(files): four stellar-quality improvements to file-viewer split

- Extract useBlobUrl hook shared by AudioPreview and VideoPreview,
  eliminating ~30 lines of duplicated state/effect logic
- Stabilize markSavedContent with useCallback (matches setDraftContent)
- Stabilize handleEditorChange with useCallback([setDraftContent])
- Fix pptx static render effect deps: drop redundant dataUpdatedAt
  (already encoded in cacheKey) and unused workspaceId

* test(files): extract pure modules and add 122-test suite for file viewer logic

Extract TextEditorContentState machine and file category resolution into
plain .ts modules (text-editor-state.ts, file-category.ts) so they can
be unit-tested without React or Next.js overhead. Update component files
to import from the extracted modules, eliminating code duplication.

Add two test files:
- text-editor-state.test.ts: 32 tests covering resolveStreamingEditorContent,
  the reducer (edit / save-success), and syncTextEditorContentState across
  all phases (uninitialized, ready, streaming, reconciling) including
  reference-equality short-circuit checks for zero-allocation paths
- file-category.test.ts: 90 tests covering MIME-type routing for all 8
  categories, extension fallback, MIME-priority-over-extension, and
  case-insensitive extension handling

* fix(files): add key to IframePreview and use monotonic seq for streaming PDF key

- Add key={file.id} to IframePreview so React remounts on file switch,
  preventing stale renderError from persisting across different files
- Replace key={streamingBuffer.byteLength} with a monotonic sequence
  counter so same-size successive PDF compilations still trigger a remount

* fix(files): restore getFileExtension import dropped during refactor

* fix(files): clear loadError on PDF success and fix streaming null-flash

- pdf-viewer: add setLoadError(null) in onLoadSuccess so the toolbar
  is not permanently hidden after a failed-then-successful PDF load
- file-viewer: consolidate streaming-mode rendering so the debounce
  period (before rendering=true) shows a skeleton instead of null

* refactor(files): cleanup pass — effect, callback, state, and design fixes

- text-editor: replace sync-external useEffect with "adjust during render"
  pattern so the state machine advances immediately instead of after a paint
- text-editor: remove unnecessary useCallback from markSavedContent (no observer)
- files: narrow deleteTargetFile state to {id, name} — only those fields are used
- files: remove uploadFile (mutation object) from useCallback deps — .mutateAsync is stable
- files: remove unnecessary useCallback from handleNavigateToFiles (no observer)
- files: replace raw <button> with emcn Button for "Clear all filters" action
2026-04-28 13:07:24 -07:00
WaleedandClaude Opus 4.7 dc20229954 perf(docker): use turbo prune for app.Dockerfile (#4322)
* perf(docker): refactor app.Dockerfile to use turbo prune

Replaces manual workspace package.json copies with `turbo prune sim --docker`,
matching the canonical Vercel/Turborepo monorepo pattern (and the existing
realtime.Dockerfile).

- New `pruner` stage emits `out/json` (manifests + lockfile) and `out/full`
  (sources) for only the packages sim actually depends on.
- `deps` stage installs from the pruned manifest, so cache invalidates only
  when package.json/bun.lock change — not on source edits.
- Drops 24 lines of brittle manual COPYs (one per workspace package).
- Single full install in deps (no --omit=dev) so build-time devDeps like
  tailwindcss/postcss are available — replaces the earlier hotfix that did
  a second install in the builder stage.

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

* chore(docker): pin turbo to 2.9.6 in pruner stage

Match the version locked in package.json so pruner output is reproducible
across builds.

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 13:06:51 -07:00
Theodore Li 69dc2f04b8 fix(mcp): Use SDK web-standard transport for copilot mcp (#4320)
* fix(api): return 499 on copilot mcp user aborts

* fix(mcp): fix copilot mcp response
2026-04-28 16:05:19 -04:00
3afcad2189 fix(short-input): hide selected text to prevent overlay collision (#4318)
* v0.6.29: login improvements, posthog telemetry (#4026)

* feat(posthog): Add tracking on mothership abort (#4023)

Co-authored-by: Theodore Li <theo@sim.ai>

* fix(login): fix captcha headers for manual login  (#4025)

* fix(signup): fix turnstile key loading

* fix(login): fix captcha header passing

* Catch user already exists, remove login form captcha

* fix(short-input): hide selected text to prevent overlay collision

The ShortInput component uses a transparent input layer beneath a
formatted overlay. When users selected text and scrolled horizontally,
the browser's ::selection pseudo-element overrode `color: transparent`,
making raw unformatted text visible and causing visual collision with
the overlay.

Adding `selection:text-transparent` keeps the input text invisible
under selection, while the overlay continues to display the correctly
formatted highlighted text.

Fixes #3389

Co-Authored-By: Octopus <liyuan851277048@icloud.com>

---------

Co-authored-by: Waleed <walif6@gmail.com>
Co-authored-by: Theodore Li <theodoreqili@gmail.com>
Co-authored-by: Siddharth Ganesan <33737564+Sg312@users.noreply.github.com>
Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
Co-authored-by: Theodore Li <theo@sim.ai>
Co-authored-by: octo-patch <octo-patch@github.com>
2026-04-28 10:57:16 -07:00
WaleedandClaude Opus 4.7 c400e59ea6 feat(sap_s4hana): add get_material_document and fix supplier invoice key order (#4317)
* fix(sap_s4hana): require non-empty items in create_purchase_order

Why: SAP A_PurchaseOrder POST silently fails or returns opaque errors
without to_PurchaseOrderItem entries. Block already required this body
but the tool marked it optional and didn't validate items presence —
mismatched contract with create_sales_order / create_purchase_requisition.

Also clarifies the deliveryDocument placeholder to show both outbound
and inbound number ranges.

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

* feat(sap_s4hana): add get_material_document and fix supplier invoice key order

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

* fix(sap_s4hana): align material doc key order in description and require purchase order body type

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-27 23:41:56 -07:00
Vikhyath Mondreti 2e3de9ac8a feat(governance): external workspace users from outside org (#4313)
* feat(governance): external workspace users from outside org

* update docs

* address comments

* edge case improvements

* remove unused fallback

* address comments

* add outbox for seat reduction

* fix edge case with org join after invite

* add server side batch invites for workspace

* use zod schema for route
2026-04-27 22:07:41 -07:00
Theodore Li 154b9d0883 fix(vm): categorize user or server side errors (#4283)
* fix(vm): categorize user or server side errors

* recategorize function syntax errors as 4xx
2026-04-27 22:27:50 -04:00
WaleedandClaude Opus 4.7 c95ac3bc23 improvement(browser-use,stagehand): expose live session URLs (#4314)
* improvement(browser-use,stagehand): expose live session URLs and align with latest API specs

- Browser Use: switch to v2 camelCase schema, fetch live URL from sessions endpoint, add startUrl/maxSteps/allowedDomains/vision/flashMode/thinking/systemPromptExtension/structuredOutput/metadata params, surface liveUrl/shareUrl/sessionId outputs
- Stagehand: fetch Browserbase debug URL, add mode/maxSteps params, surface liveViewUrl/sessionId outputs, bump @browserbasehq/stagehand to ^3.2.1, update to claude-sonnet-4-6

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

* fix(browser-use): respect API default for highlightElements

Only send highlightElements when user explicitly toggles it; previously defaulted to true which silently overrode the v2 API default of false.

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

* fix(browser-use,stagehand): address PR review feedback

- Browser Use: fetch liveUrl during polling once sessionId is known, instead of immediately after task creation. Handles tasks started without profile_id (where sessionId isn't returned in create response) and ensures session is active before fetching.
- Stagehand: coerce empty/whitespace maxSteps strings to undefined so they're dropped from the request body instead of failing zod validation as ''.

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

* fix(stagehand): preserve liveViewUrl and sessionId on agent error

If the agent throws after Browserbase session init succeeds, callers can still surface the live view / session ID for debugging.

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

* fix(browser-use): coerce empty maxSteps strings to undefined

Mirrors the Stagehand block's handling so a cleared field doesn't pass through as ''.

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

* fix(browser-use): skip metadata when empty

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-27 18:02:51 -07:00
Theodore Li ca814f021d fix(ui): display file upload error messages (#4315)
* fix(ui): display file upload messages

* Address pr comments
2026-04-27 18:58:41 -04:00
WaleedandClaude Opus 4.7 2502369122 feat(integrations): SAP S/4HANA (#4301)
* feat(integrations): SAP S/4HANA tools, block, and proxy with multi-deployment support

* fix(sap_s4hana): address PR review comments

- Validate baseUrl/tokenUrl in Zod schema and at runtime to prevent SSRF
  (https-only, deny loopback/link-local/cloud-metadata hosts)
- Cap proxy token cache at 500 entries with LRU eviction
- Add 30s timeout to outbound token, CSRF, and OData fetches
- Make parseJsonInput return T | undefined so missing input is type-safe
- Reset authType when deploymentType changes and surface OAuth fields
  whenever auth is not basic, so cloud_public users always see clientId/
  clientSecret after switching from a basic-auth private deployment
- Reject OData service names that are not uppercase identifiers and
  paths containing ".." or "." traversal segments

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

* fix(sap_s4hana): allow versioned service names; tighten proxy SSRF defenses

- Permit ";v=NNNN" suffix on ServiceName regex so the four delivery tools
  (API_OUTBOUND_DELIVERY_SRV;v=0002, API_INBOUND_DELIVERY_SRV;v=0002) pass
  schema validation
- Restrict subdomain to RFC 1123 label characters and region to lowercase
  alphanumeric short codes; run the constructed cloud_public host through
  assertSafeExternalUrl so a crafted subdomain (e.g. "evil.com#") cannot
  redirect requests carrying SAP credentials
- Block RFC-1918 (10/8, 172.16/12, 192.168/16), 127/8, 169.254/16, and
  0.0.0.0 via isPrivateIPv4, plus IPv4-mapped IPv6 variants
  (::ffff:10.0.0.1, ::10.0.0.1) so private internal hosts cannot be
  reached from baseUrl, tokenUrl, or the resolved cloud_public URL

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

* fix(sap_s4hana): catch hex-form IPv4-mapped IPv6 in SSRF check

The WHATWG URL parser normalizes IPv4-mapped IPv6 addresses to hex form
(e.g. [::ffff:169.254.169.254] → [::ffff:a9fe:a9fe]), which slipped past
the dotted-decimal-only extractor. Decode the trailing two 16-bit hex
groups back into IPv4 octets and run them through isPrivateIPv4. Also
add isPrivateOrLoopbackIPv6 so pure IPv6 loopback (::, ::1), unique
local addresses (fc00::/7), and link-local (fe80::/10) cannot be reached.

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

* fix(sap_s4hana): scope CSRF metadata fetch and isolate token cache by secret

- buildOdataUrl skips request query params when called with an internal
  pathOverride so the /$metadata CSRF probe never carries user OData
  options ($filter, $top, $select), which were causing write operations
  through the generic odata_query tool to fail.
- tokenCacheKey now mixes a sha256 hash of clientSecret into the cache
  key so two tenants sharing the same tokenUrl + clientId but different
  secrets get isolated entries (no cross-tenant token leak).

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

* fix(sap_s4hana): reject ?/# in service path; trim long update tool descriptions

- ServicePath validator now rejects "?" and "#" so a caller can't smuggle
  query options through the path field (e.g.,
  "/A_BusinessPartner?$format=atomsvc"); the Zod refine now reports
  ".." / "." segments, "?", and "#" together.
- Update Customer / Update Supplier / Update Purchase Requisition tool
  descriptions exceeded the docs generator's 600-char regex window, so
  they were rendering with empty descriptions on the integrations
  landing page. Trimmed them to fit while keeping the limited-fields
  note and the If-Match guidance, then regenerated integrations.json
  and tool docs.

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

* fix(sap_s4hana): reject percent-encoded path traversal; widen Set-Cookie split

- ServicePath now also rejects %2e/%2E, %2f/%2F, %5c/%5C, %3f/%3F, %23
  so a caller cannot smuggle ".." / "." / "/" / "\" / "?" / "#" past the
  validator and have SAP's ABAP/ICM gateway decode them server-side.
- joinSetCookies fallback regex now allows the ", " separator that's
  used when multiple Set-Cookie values are folded onto one header line
  (older runtimes without Headers.getSetCookie). Prevents CSRF cookies
  from being concatenated into a single value during write operations.

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

* fix(sap_s4hana): preserve $ in OData query params; reject empty items array

- buildOdataUrl now constructs query strings manually with
  encodeURIComponent and restores literal "$" so OData system options
  ($filter, $top, $select, $expand, $orderby, $skip, $format) reach
  SAP and any intermediary proxies/WAFs as-is, not as "%24filter".
  URLSearchParams was percent-encoding "$" to "%24" which most ICMs
  decode but some intermediaries silently drop, returning unfiltered
  results.
- create_sales_order now rejects an empty items array (matches
  create_purchase_requisition) so callers get a clear client-side
  error instead of an opaque SAP validation failure on the deep-insert.

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

* fix(sap_s4hana): ignore baseUrl on cloud_public to prevent token redirection

Why: resolveHost previously preferred baseUrl unconditionally. A caller
sending deploymentType=cloud_public with a baseUrl pointing elsewhere
would obtain a real SAP UAA token, then forward it as Bearer to the
attacker host. Zod superRefine did not validate baseUrl for cloud_public.

Fix: resolveHost now constructs the SAP host from subdomain when
deploymentType is cloud_public and only uses baseUrl for cloud_private
and on_premise (where it is already SSRF-checked in superRefine).

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

* fix(icons): use useId for SapS4HanaIcon and PipedriveIcon gradients

Why: hardcoded SVG gradient/mask IDs collide when an icon renders more
than once on a page (e.g. integrations listing). All other icons in this
file use React's useId() — these were inconsistent.

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

* icons

* fix(icons): use useId for AWS-style icon gradients

Why: IAMIcon, IdentityCenterIcon, STSIcon, SESIcon, and SecretsManagerIcon
all used hardcoded `id='xxxGradient'` values that collide when an icon
renders more than once on a page (e.g. integrations listing).

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

* fix(sap_s4hana): ignore tokenUrl on cloud_public to prevent UAA redirection

Why: resolveTokenUrl previously honored caller-supplied tokenUrl
regardless of deploymentType, mirroring the same redirection class as
the prior baseUrl bug. A cloud_public caller could send tokenUrl to an
attacker host, causing the proxy to POST clientId:clientSecret as Basic
auth to it. superRefine for cloud_public did not validate tokenUrl.

Fix: derive UAA URL from subdomain+region for cloud_public; only honor
tokenUrl for cloud_private/on_premise (already SSRF-checked).

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

* fix(icons): remove unused mask in PipedriveIcon

Why: the <mask> element had no consumer (no mask='url(#...)' anywhere
in the SVG), so both it and the maskId variable were dead code.

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-27 15:07:00 -07:00
Waleed 8266f0afdb fix(security): rate limit chat OTP + validate mothership proxy endpoint (#4312)
* fix(security): rate limit chat OTP endpoint to prevent email bombing

* fix(security): validate mothership proxy endpoint to block path traversal

* fix(security): address greptile feedback on OTP rate limiting
2026-04-27 14:51:58 -07:00
Waleed 896a00ae31 fix(security): require internal API key for copilot training endpoints (#4311) 2026-04-27 14:30:47 -07:00