upstream-sync: v0.7.38…v0.7.43 → stack tip (2026-08-06-5) - #689
Draft
utcarshsrivastava-collab wants to merge 158 commits into
Draft
upstream-sync: v0.7.38…v0.7.43 → stack tip (2026-08-06-5)#689utcarshsrivastava-collab wants to merge 158 commits into
utcarshsrivastava-collab wants to merge 158 commits into
Conversation
…imstudioai#5697) * chore(pii): remove GLiNER/GPU image + add spaCy-skip fast path to CPU server * feat(pii): restrict block-output redaction to regex-only entities * fix(pii): derive spaCy-NER set from registry + skip fast path when score_threshold set * fix(pii): include ORGANIZATION in app-side NER set (align with server)
… sub management (simstudioai#5680) * improvement(webhooks): external subscription management * ui/ux * remove test file * fix tests * address comments * address comments * update to grain v2 api * improvement(grain): hide auto-registered webhook URL on v2 triggers * Revert "improvement(grain): hide auto-registered webhook URL on v2 triggers" This reverts commit c89660c. * address comments * address comments * address rollback * fix grain v2 * fix more comments
…i#5701) * improvement(ci): decouple image builds from the test gate - build-amd64 now starts immediately and pushes only sha tags (ECR :sha, GHCR :sha-amd64). The EventBridge deploy triggers filter on exactly the latest/staging/dev ECR tags, so nothing deploys from these pushes. - New promote-images job retags sha -> latest/staging (and GHCR latest-amd64/version-amd64) via buildx imagetools once tests and migrations pass — a seconds-long manifest copy instead of rebuilding after the gate. Cuts push-to-deploy from ~13.5 to ~7 minutes. - Split the Next.js production build out of test-build into a parallel Build App job with its own sticky-disk keys, cutting PR feedback from ~5.5 to ~3.5 minutes. - create-ghcr-manifests and process-docs now gate on promote-images. * improvement(ci): harden promotion — atomic retag, stale-run guards, gate all mutable GHCR tags Review follow-ups: - promote-images is a single job (not a matrix): verifies all four :sha manifests exist before moving any deploy tag, so a missing image can't cause a partial mixed-version deploy - stale-run guard on promote-images and create-ghcr-manifests: re-running an old run no longer retags latest/staging back to stale code (a one-click prod rollback); superseded runs skip mutable tags with a warning while immutable sha/version tags still publish - ARM64 build now pushes only the immutable :sha-arm64 tag; latest-arm64 and version tags moved behind the gate into create-ghcr-manifests (closes the pre-existing hole where a failing run moved latest-arm64) - dropped dead detect-version needs from both build jobs - sticky-disk comment corrected (clone + last-writer-wins, not exclusive mounts); build job shares warm bun/node_modules disks, keeps its own turbo cache key so test/build entries don't evict each other
…ew loop (simstudioai#5695) * fix(babysit): resolve merge conflicts against staging during the review loop * fix(babysit): trim merge-conflict handling to essentials * fix(babysit): gate conflict-resolution pushes and handle UNKNOWN mergeable * fix(babysit): bound persistent UNKNOWN mergeable state and clarify step skip * fix(babysit): merge instead of rebase to resolve conflicts, drop force-with-lease * fix(babysit): don't skip pending review findings when resolving a merge conflict * fix(babysit): spot-check commit hygiene before a merge-conflict push * fix(babysit): commit merge-conflict fixes before pushing, bound UNKNOWN before waiting * fix(babysit): run pre-push checks before committing the merge-conflict fix * fix(babysit): bound persistent CONFLICTING state and fix pre-push check order * fix(babysit): reconcile hard rule with step 2's merge-conflict sync exception * fix(babysit): page all review threads before branching on mergeable state * fix(babysit): cut merge-conflict handling down to the essentials * fix(babysit): simplify merge-conflict handling to a minimal step
…, attachment upload (simstudioai#5702) * feat(clickup): add ClickUp integration with OAuth + API-token auth, 23 tools, block, and attachment upload - 23 tools covering tasks (create/get/update/delete/list/search), comments (create/get/update/delete), attachment upload, tags, members, custom fields, and the workspace/space/folder/list hierarchy - OAuth provider wiring (authorization-code flow, non-expiring tokens) plus clickup-service-account token-paste credential (personal pk_ API tokens), with a shared clickupAuthorizationHeader helper (pk_ tokens sent bare, OAuth tokens as Bearer) - File upload follows the internal-route pattern: contract-validated /api/tools/clickup/upload-attachment builds the multipart form and returns UserFiles - ClickUp block with per-operation subBlocks, canonical file param, BlockMeta templates/skills, and gradient brand icon - Generated integration docs page + hand-written service-account guide * fix(clickup): apply validation-audit fixes across tools, block, and upload route - Map documented task fields that were dropped: markdown_description, subtasks, watchers, custom_fields, time_spent, folder, space — making the include_subtasks / include_markdown_description options observable - Expand verified filters: assignees/tags/due-date ranges on get_tasks and search_tasks, include_closed on search_tasks; add due_date_time / start_date_time flags and update-task assignee add/remove - Guard update_comment against an empty body and require comment text in the block; prefer markdown_content over content on create_list and make markdown reachable for lists in the UI - Drop the unverified 'required' field from custom-field outputs; read both err and error keys from ClickUp error bodies; correct notify_all wording - Upload route: 100MB size cap, shared attachment mapper with full documented response fields (version, thumbnails), base-URL constant * fix(docs): restore clickup-service-account guide and shield it from doc generation The generator prunes integration pages it does not derive from blocks; add the hand-written ClickUp API-token guide to HANDWRITTEN_INTEGRATION_DOCS so regeneration cannot delete it. * fix(clickup): address review findings — dedupe catalog entries, config-time list parent validation, upload memory cap, unique icon gradient ids - Remove duplicated clickup entries in docs meta.json and integrations.json introduced by a double docs regeneration - Add a Location dropdown for Get Lists / Create List so the folder ID or space ID is conditionally required at configuration time instead of failing at run time - Pass the 100MB cap into downloadServableFileFromStorage so oversized files abort during download instead of after full buffering - Use useId()-derived SVG gradient ids for ClickUpIcon in both icon files * chore(clickup): format integrations.json entry per biome * improvement(clickup): final validation-pass refinements across tools and block - create_task: add doc-backed sprint points param (parity with update) - get_tasks/search_tasks: expose include_markdown_description - update_task legacy numeric priority in list responses mapped instead of dropped; create_comment omits absent response fields instead of emitting sentinel ''/0 values - order_by only sent when explicitly chosen (Default sentinel); comment text no longer UI-required for update_comment (resolve-only and assignee-only updates are valid per the tool contract, which still rejects an empty body) - add_tag_to_task sends no request body per docs; upload tool tolerates non-JSON error responses * fix(clickup): tolerate nested user wrapper in member mapping The task/list member endpoints document a flat member object; accept the workspace-members-style nested { user: {...} } wrapper as well so both shapes map correctly. * fix(clickup): map size-limit errors from download/compile to a 400 upload-size response downloadServableFileFromStorage enforces maxBytes on both the raw download and the resolved (compiled) artifact via PayloadSizeLimitError; catch it in the route so oversized content returns the intended 400 instead of bubbling to the generic 500 handler. * feat(clickup): add custom field values, checklists, and time tracking (15 tools, 38 total) - Set/remove custom field values on tasks (PUT/DELETE /task/{id}/field/{field_id}); block value input parses JSON for structured field types, plain values pass through - Checklist CRUD: create/rename/reorder/delete checklists and create/update/ delete checklist items (assign, resolve, nest), mapped from the documented {checklist} response shape - Time tracking: list entries in a date range (assignee/location filters, task-tag and location-name includes), create/update/delete entries, start/ stop timers, and read the currently running timer; entries mapped from the documented data envelope with negative-duration running semantics - Block gains 15 operations with conditionally-required fields, timestamp wand configs, tri-state billable/resolved dropdowns, and a single-location filter selector matching the API's one-location-filter rule * fix(clickup): new-tools audit fixes — POST for set custom field value, tolerant time-entry envelopes, richer mappings - Set Custom Field Value uses POST per the live reference OpenAPI (the llms mirror shows PUT; the reference console spec is authoritative) - delete_time_entry maps the documented array envelope; create_time_entry tolerates both data-wrapped and flat echo bodies - Time entries surface task_tags and task_location so the include switches are observable; checklists carry date_created - Custom field value input parses any JSON literal (numbers, booleans, arrays, objects) and passes plain text through - Update Time Entry supports duration edits; single-assignee time ops get their own field so a comma-separated list can't silently NaN out * fix(clickup): send explicit date-time flags whenever a date is set The due/start date-time switches previously only transmitted true; a timed date could never be flipped back to date-only. The flag is now sent as an explicit boolean whenever the corresponding date is provided and omitted otherwise. * improvement(clickup): final per-tool audit polish — checklist item children, tolerant comment date - Checklist items surface the documented children array of nested item IDs - create_comment tolerates a string-typed date in the response * fix(clickup): reject empty update_task bodies with a clear local error, matching sibling update tools
…alesforce + Pipedrive API tokens (simstudioai#5690) Second service-account kind (follow-up to simstudioai#5682): client-credential pairs (client ID + secret + org identifier) that mint short-lived tokens at execution — in-memory cache with ciphertext-fingerprint validation (rotation-correct across instances), single-flight coalescing, 30s failure memo, no refresh-token storage. - Zoom Server-to-Server OAuth, Box Client Credentials Grant, and Salesforce client-credentials (integration user) are the first minters; Salesforce's live instance_url rides the existing instanceUrl plumbing so all 40 tools work unchanged - Pipedrive lands as a token-paste provider with explicit authStyle threading: descriptor declares x-api-token, one shared header helper drives all 18 tools + both selector routes, OAuth Bearer behavior untouched - SSRF-allowlisted Salesforce My Domain host (production/sandbox/developer partitioned domains); ENOTFOUND maps to site_not_found, EAI_AGAIN stays provider_unavailable; 408/429 from token endpoints never blamed on creds - Create route forwards clientId/clientSecret/orgId to the builder, pinned by a route-level regression test - Zoom user-scoped tools document that server-to-server tokens don't support 'me' - 4 setup-guide docs pages verified against current vendor flows (incl. Salesforce External Client Apps — the classic Connected App wizard is disabled by default since Spring '26); integrations sidebar gains a Service Accounts & API Keys section
…ta> outputs (simstudioai#5700) * feat(start-block): add run metadata toggle with trusted <start.metadata> outputs * fix(start-block): fail-soft email lookup, consistent nested metadata propagation, toggle parsing parity * improvement(start-block): enumerate metadata fields in toggle description for agent schema * fix(start-block): carry metadata chain through toggle-off children, preserve fail-soft null email * fix(start-block): recover metadata chain from seeded start output after resume
…cher never flashes loading (simstudioai#5706)
…, phases, fields, time tracking, spaces, and invoices (simstudioai#5709) * feat(rocketlane): Rocketlane integration — 64 tools across projects, tasks, phases, fields, time tracking, spaces, and invoices * fix(rocketlane): allow clearing optional fields on update, require a user reference for time-off and placeholder assignment * chore(rocketlane): trigger fresh review round * fix(rocketlane): require an owner reference when creating a project, matching the API contract
…dge-base connector (simstudioai#5708) * feat(clickup): webhook triggers with auto-managed subscriptions + hierarchy selectors * feat(clickup): KB connector (Docs v3), selector-route hardening, subblock migrations, registry-check regex fix * test(clickup): webhook provider handler tests + redact create-response secret in error logs * fix(clickup-connector): trim final page to maxDocs cap with precise listingCapped semantics * chore(clickup): lint formatting * fix(clickup): restore list-op location requiredness, split listSpaceId migration target, surface failed webhook rollback * fix(clickup): clickup.lists selector accepts listSpaceId context like clickup.folders * fix(clickup): integer-only maxDocs and location filters, depth-aware doc headings, most-specific-location hints
…ts, fork chat, inline questions (mothership v0.8) (simstudioai#5410) * feat(scout): add scout agent * fix(contracts): update contracts to include scout agent * feat(copilot): search agent (research+scout merge) + read-only table/KB tool handlers Mirrors mothership dev f90f9b05: - regenerated tool-catalog/tool-schemas mirrors (search trigger replaces research + scout; QueryUserTable / SearchKnowledgeBase entries) - queryUserTableServerTool / searchKnowledgeBaseServerTool: read-only wrappers delegating to the full user_table / knowledge_base handlers with hard operation allowlists (and outputPath export rejection on query_user_table) - display maps: 'search' agent label/title/icon added; research + scout entries retained so historical transcripts keep rendering - Search.id replaces Research.id in LONG_RUNNING_TOOL_IDS (it inherits research's long crawls) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(copilot): run_code compute-only handler; docs lint fix Mirrors mothership dev db60da94: run_code is the compute-only variant of function_execute for the search agent — same sandbox and inputs, no outputs.files / outputTable, so it cannot create or overwrite workspace resources. Wrapper handler hard-rejects the write vectors and delegates to executeFunctionExecute; run_code is deliberately absent from OUTPUT_PATH_TOOLS and the table output post-processor, so the name gating blocks writes even for leaked args. Added to LONG_RUNNING_TOOL_IDS, display title/icon maps, and the regenerated catalog/schema mirrors. Also removes two ineffective biome suppression comments in the docs workflow-preview (the rule doesn't fire in the docs app config). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(copilot): failed tool calls must surface their error in terminal data A failed handler result that carried a defined-but-empty output (the app-tool executor's 'Tool not found' ships output: {}) won the priority race in getToolCallTerminalData, so the resume payload's data — the only thing the model reads — was a bare {} with the error text dropped. The search agent retried run_code 20+ times blind against a stale server because every failure rendered as empty instead of 'Tool not found'. Failed calls now always carry error in their terminal data: merged into object outputs, wrapped alongside non-object outputs, preserved when the output already has an error field. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(chat): render inline question tags from the agent in chat * fix(chat): let inert multi-step questions browse all prompts * improvement(chat): guard question answer formatting against sparse arrays * chore(copilot): drop user_memory from generated contracts and tool display Companion to mothership 8ae32e97 (user_memory tool removed — the feature no longer exists). Regenerates the mothership contract mirrors via generate-mship-contracts.ts, which also picks up the pending telemetry contract additions (gen_ai.agent.name labels, llm.client.context_tokens, llm.client.compactions, llm.request.compaction_trigger, llm.compaction.pause, gen_ai.usage.context_tokens), and removes the user_memory display title. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * improvement(chat): answered question card becomes the user turn; two select types only UI ordering: answering a question card no longer echoes a duplicate user bubble. The combined answer still goes on the wire as a user message, but the chat pairs it back to its card (strict 'Prompt — Answer' match, now uniform for single questions too) and renders the card as the answered recap — the card IS the user turn, and the next assistant message streams below it. The pairing is derived from the transcript, so live and reloaded renders are identical; a dismissed card followed by an unrelated typed message does not match and renders normally. Messages ending with a question card also drop the copy/thumbs actions row — the card is an input surface, not a reactable assistant turn. Question types are now single_select and multi_select only: text is removed (the free-text 'Something else' row covers it) and confirm collapses into single_select with Yes/No options. multi_select rows toggle with a check and the free-text row's arrow submits the step; answers are comma-joined labels plus any typed entry. Agent-supplied catch-all options ('Other', 'Something else', 'None of the above') are stripped at parse — the card always provides its own free-text row; a question left with no real options is invalid. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * improvement(chat): question cards are single_select only Removes multi_select (and its toggle/check UI). The card is one shape: pick one option or type into the always-present 'Something else' row. Catch-all stripping and the transcript pairing/recap behavior are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * improvement(chat): bring back multi_select question cards Re-adds multi_select with a reworked interaction: option rows carry real checkboxes (emcn Checkbox chrome) instead of numbers and arrows, an option-styled Submit row confirms the step, and the "Something else" row reads as a plain option until clicked — then it becomes the focused text box, auto-checks, and can be unchecked without losing the typed text (blur with nothing typed reverts it). single_select behavior, catch-all stripping, and the transcript pairing/recap format are unchanged; multi_select answers are the checked labels comma-joined. * chore(copilot): regenerate mothership contract mirror (chat blob span attrs) * chore(copilot): regenerate mothership contract mirror (chat blob metrics) * feat(secrets): make output of generate api key a secret * feat(cli): add mkdir, mv, cp to mship tool set * feat(fork-chat): add fork chat to mothership * fix(fork-chat): fix messageid handling in fork chat * feat(credentials): agent-initiated oauth credential reconnect (simstudioai#5488) * feat(credentials): agent-initiated oauth credential reconnect * fix(credentials): address reconnect review findings * improvement(credentials): log when connect draft name lookups degrade * fix(conflicts): remove migration * fix(conflicts): fix conflicts * fix(fork-chat): add migrations back * fix(ci): fix lint * fix(ci): fix bad import * fix(vfs): fix 500 char limit in vfs for skills and custom tools * feat(copilot): gate user skills to explicit slash-attach (simstudioai#5536) Stop the mothership from adopting a workspace user-skill on its own: - Remove the load_user_skill tool and its three payload callers (chat payload, mothership execute route, inbox executor); delete lib/mothership/skills.ts + its test. Skills no longer autoload as the agent's own instructions. - Rename the workspace "## Skills" inventory to "## Agent Block Skills — NOT FOR YOU" with a one-line guardrail so a skill's description (e.g. "respond like a pirate") is not treated as an instruction. Skills reach the model as behavior only via explicit /-attach. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lots-of-things): lots of things * feat(subagents): add persistent subagents * fix(copilot): let edit_workflow set knowledge-base tag filters, and stop it clearing them (simstudioai#5546) * fix(copilot): persist KB tag subblocks as JSON strings from edit_workflow The edit_workflow tool normalizes array-with-id subblocks (via normalizeArrayWithIds) but only re-stringifies the keys listed in JSON_STRING_SUBBLOCK_KEYS. `tagFilters` (knowledge-tag-filters) and `documentTags` (document-tag-entry) were missing, so agent-authored tag filters were stored as raw JSON arrays while those UI components read their value with JSON.parse (expecting a string). The result: an agent edit to a Knowledge block's tag filter persisted correctly but rendered as an empty filter in the editor (JSON.parse on an array throws -> []). - Add `tagFilters` and `documentTags` to JSON_STRING_SUBBLOCK_KEYS so edit_workflow stores them in the same shape the UI writes. - Make both components' parsers tolerate an already-parsed array on read, self-healing values already persisted in the broken (array) shape. Search execution was unaffected (parseTagFilters accepts arrays), so the value was never lost — only the editor render and round-trip were broken. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(copilot): expose KB tag definitions in VFS meta.json Surface each knowledge base's defined tags (displayName -> tagSlot) inline in its meta.json via serializeKBMeta, loaded in one batched query (loadKbTagDefinitions), so the agent can bind a knowledge-tag filter to a real tag slot instead of guessing a tag name it cannot otherwise see. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(copilot): stringify KB tag subblocks on the nested-node edit path The nested-node merge path normalized array-with-id subblocks but never re-serialized the JSON_STRING_SUBBLOCK_KEYS, so editing a block nested in a loop/parallel container still persisted tagFilters/documentTags (and conditions/routes) as raw arrays -- the exact shape the subblock components cannot JSON.parse. Route all four write paths through a single normalizeSubblockValue helper so the normalize and re-stringify steps cannot drift apart again, and extract the duplicated string-or-array read logic into parseJsonArrayValue. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(copilot): tighten subblock serialization helpers Derive KbTagDefinitionSummary from the canonical TagDefinition instead of restating its fields, make parseJsonArrayValue generic so callers drop their `as T[]` casts, and unexport the three builders helpers that no longer have consumers outside the module now that normalizeSubblockValue fronts them. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(copilot): stop stripping tagFilters/documentTags from the agent's workflow view sanitizeForCopilot dropped `tagFilters` and `documentTags` from the workflow state the agent reads (workflows/{name}/state.json), while edit_workflow is allowed to write both. The field was therefore write-only: on a follow-up edit the agent read back an absent field, concluded no filter was set, and cleared the user's tag filter. The redaction was introduced for workflow *export* (simstudioai#1628) and is already enforced there by sanitizeWorkflowForSharing's key list. The duplicate in the copilot-only sanitizeSubBlocks was redundant for export and destructive for the agent. Removes it and pins the contract with a regression test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(copilot): reject malformed KB tag values instead of clearing the filter `knowledge-tag-filters` and `document-tag-entry` had no arm in the `edit_workflow` input validator, so they fell through to the pass-through default. Any non-array value the agent supplied -- a double-encoded JSON string, an object, an unparseable string -- reached `normalizeSubblockValue`, where `normalizeArrayWithIds` coerces unparseable input to `[]`. The write path then persisted `"[]"` over the tag filter the user had configured. `condition-input` and `router-input` already guard against exactly this and return an actionable error to the model. Extend that arm to cover the two KB subblock types. It keys on subblock type, so the unrelated `tagFilters` short-input on the Algolia block is unaffected. `null`/`undefined` and empty arrays still clear the field, so intentional clears keep working. Also wrap `loadKbTagDefinitions` in try/catch. Tag definitions are an optional meta.json enrichment, but the query ran inside the top-level `Promise.all`, so a transient failure would reject the entire workspace VFS materialize and leave the agent unable to read any file. Now it degrades to a meta.json without tag definitions, matching the sibling materializers. Adds regression tests for both, plus the first tests for `parseJsonArrayValue`, the helper that keeps pre-fix raw-array rows readable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(copilot): collapse duplicate JSON-array parsing in edit-workflow builders `normalizeArrayWithIds` and `normalizeConditionRouterIds` each hand-rolled the same "accept a raw array or the JSON string these subblocks persist" parse. Extract `parseJsonArray`, which returns null when the value is neither, so each caller keeps its own distinct fallback: `[]` for the former, the untouched original value for the latter. Behavior-preserving. An empty array is truthy, so `[]` and `"[]"` still parse through rather than hitting either fallback. `validation.ts` has a third copy, but `builders.ts` already imports from it, so sharing the helper across the two would introduce an import cycle. Left as is. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(copilot): specify tag name and legal operators in KB meta.json `tagDefinitions` exposed `displayName`, but a `tagFilters` entry must carry the key `tagName`. An entry written with `displayName` passes validation and persists, then filters nothing -- a silent failure. Rename the field at the serializer boundary; the DB column is untouched. Also emit the operators legal for each tag's `fieldType`, reusing `getOperatorsForFieldType`. `between` is valid for number and date but not for text or boolean, and the agent has no way to infer that. An unrecognized fieldType yields an empty list rather than throwing. Still unspecified, and deliberately out of scope: a filter entry's value key is `tagValue` (but `value` on documentTags), and `between` needs `valueTo`. Those describe the subblock entry shape, not the knowledge base, so meta.json is the wrong place for them. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(copilot): pass a nullish subblock clear through instead of serializing "[]" `validateValueForSubBlockType` accepts null as an explicit clear, but `normalizeSubblockValue` then ran it through `normalizeArrayWithIds`, which coerces any non-array to `[]`, and persisted the string "[]". No data is lost either way -- "[]" and an absent field both mean "no filters". But it left the field present when the caller asked for it to be unset, so `sanitizeForCopilot` showed the agent an empty filter rather than an absent one, contradicting the absent-means-unset invariant the sanitizer documents. It also made Algolia's `if (params.tagFilters)` see a set value, since "[]" is truthy. An explicitly empty array still serializes to "[]" -- clearing with a value is distinct from clearing by omission. Reported by Cursor Bugbot on simstudioai#5546. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(changes): huge changes * fix(subagents): lanes * fix(mship): transcript stuff * fix(subagents): thinking lanes * fix(superagent): fix superagent tools and checkpoints * fix(scope): scope subagent tools * fix(lint): fix lint * chore(db): regenerate workspace_files.message_id migration as 0260 on staging base * fix(superagent): fix superagent integration tools * improvement(questions): make something else a placeholder * chore(copilot): regenerate mothership contract mirror after staging rebase * feat(mship): add external mcps to mship * fix(ci): fix dev build * fix(stream): show thinking text * fix(ci): force redeploy * fix(mothership): keep chat forks outside workspace storage billing Preserve the product invariant that Mothership chat files are not charged as workspace file storage after the billing storage merge. * fix(uploads): restore listWorkspaceFiles throwOnError option dropped in rebase * fix(subagent-streaming): remove italics * fix(mothership): treat subagent lanes closed by subagent_end as settled so the between-steps thinking indicator isn't suppressed * fix(ui): thinking loader and rool names * fix(ui): add file * fix(thinking): show thinking during subagents * fix(chat): drop dead thinking-channel ternary after lanes skip thinking blocks * fix(thinking): remove thinking text * improvement(function execute): add timeout to function execute and stop showing text in subagents * fix(subagents): hide thinking text * fix(ff): move ff to go * improvement(superagent): nuke superagent * feat(main-agent): superagent into main agent * chore(db): regenerate workspace_files.message_id migration as 0262 on staging base * fix(credentials): restore reconnect params on shared createConnectDraft * fix(migrations): rebase with staging --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Emir Karabeg <emirkarabeg@berkeley.edu> Co-authored-by: Justin Blumencranz <96924014+j15z@users.noreply.github.com> Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
…oai#5710) * feat(gitlab): add access, membership, and user-admin tools Adds member, invitation, access-request, SAML group link, and user administration tools to the GitLab integration. Resource-scoped ops work against projects or groups; user-admin ops require an admin token. All tools reuse the existing host/SSRF guard via getGitLabApiBase and add a shared getGitLabResourcePath helper. * feat(gitlab): wire access operations into the GitLab block Adds the new operations to the block dropdown and tools access list, with a named access-level dropdown (enum in, integer out), first-class expires_at, a /members/all default (direct-only opt-in), resource-type selector, and member_role_id passthrough. * test(gitlab): cover access operations Covers the access_level enum-to-integer coercion, the /members/all default vs direct-only, the 409-duplicate-add soft success, invitation per-email error handling, user-status-action response parsing, and getGitLabResourcePath. * docs(gitlab): document access and membership operations * Update apps/sim/blocks/blocks/gitlab.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * fix(gitlab): address review findings - update_user now sends admin:false so the Administrator switch can demote (an untouched switch stays undefined and leaves the flag unchanged) - expose the access-level dropdown for Update Invitation - normalize comma-separated invite emails so spaced multi-email input works * fix(gitlab): make update-invitation access level optional Update Invitation now uses a dedicated dropdown that defaults to 'Leave unchanged', so updating only the expiration no longer silently resets the invitation's access level to Developer. The level is sent only when explicitly chosen. * fix(gitlab): validation pass — SAML provider param, member/invitation query filter, moderation user guard, registry order * fix(gitlab): apply 10-agent validation findings across all 62 tools - MR draft flag now applied via Draft: title prefix (GitLab has no draft body param) - update_user only sends admin when a real boolean (untouched switch serialized null and could demote admins) - add_member 409 soft-success now verified against the conflict body - auto_merge sent alongside deprecated merge_when_pipeline_succeeds - job log capped at 200k chars, file content at 1M chars, with truncated outputs - MR diffs signal hasMore beyond 100 files - wire dropped params: update_issue milestoneId, MR milestone/squash/removeSourceBranch, pipelines ref, tree ref, branches search, commits since/until/path/author, update_file lastCommitId, jobs includeRetried, create_user forceRandomPassword - complete pipeline/job status enums, access-level enums, widen stale type unions - guards: update_invitation requires a change; create_user requires a password strategy - fix double-encoding trap in path descriptions; doc-accuracy touch-ups * fix(gitlab): review round 1 — dedicated no-default access level for update member, expiration clearing via explicit empty string * fix(gitlab): explicit Clear Expiration toggle for update member/invitation * feat(gitlab): expose full documented API surface across tools and block - membership: add-by-username, remove-member cleanup flags, list-member filters (user_ids/state/seat info), invite_source - listings: search/visibility/owned/membership, assignee/milestone filters, source/target branch filters, per-domain order-by + sort direction - CI: pipeline variables + spec:inputs, manual-job variables - repo: commit authoring (start branch, author, execute flag), release tag message + asset links, cross-fork compare + unidiff, internal notes - catalog: access-governance template + member-provisioning and access-request-audit skills - hardening from 3-agent validation: declared release params, tolerate single-object asset links, NaN guard on assignee filter, null/scalar JSON rejection * fix(gitlab): tri-state controls for update-op booleans (executable flag, MR squash/remove-source-branch) --------- Co-authored-by: Marcus Chandra <mzxchandra@gmail.com> Co-authored-by: mzxchandra <129460234+mzxchandra@users.noreply.github.com> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
…udioai#5713) * improvement(ci): promote superseded first-attempt runs forward The stale-promotion guard skipped any run whose commit was no longer the branch head. If commit A passed its gate but was superseded mid-run by commit B, and B then failed tests, A's promotion was skipped and the deploy tags stayed on pre-A code with no automatic recovery (Cursor finding on simstudioai#5712). A first-attempt run promoting an ancestor of the branch head is always a forward deploy — runs on a ref are serialized by the concurrency group, so nothing newer can have promoted first. Only re-runs of superseded commits (a rollback attempt) and force-pushed-away commits are skipped. Same semantics applied to the GHCR latest-tag guard. * fix(ci): grant contents:read to create-ghcr-manifests for the compare-API guard Job-level permissions replace the workflow defaults, so packages:write alone left the guard's compare call 403ing — STATUS=unknown would have silently skipped the GHCR latest tags on every main push.
…ode pin; remove i18n workflow (simstudioai#5714) * improvement(ci): job timeouts everywhere, docs-only PR skip, event-scoped sticky disks, Node pin; drop dead i18n workflow - timeout-minutes on every runnable job (defaults ran hung jobs to the 6-hour cap — the i18n workflow burned three full 6-hour runs in Feb before its schedule was pulled) - paths-ignore on the pull_request trigger: docs content and markdown don't affect the app build or images; push runs stay unfiltered - sticky-disk keys scoped by event name so fork PR runs never share a disk with the push runs that feed production image builds - node-version pinned to 22 (was 'latest', non-deterministic) - delete i18n.yml: schedule already removed after repeated 6-hour hangs, workflow_dispatch-only since, comments stale * improvement(ci): 45m migrate timeout (covers 30m lock wait), fork-namespaced PR sticky disks - migrate.ts waits up to 30 minutes for the migration advisory lock (LOCK_ACQUIRE_DEADLINE_MS); the 15m job timeout would preempt that designed wait, so the bound is 45m - fork PRs now get their own sticky-disk namespace so an untrusted fork run can't poison the disks that trusted internal-PR runs restore
* feat(providers): add Kimi (Moonshot AI) provider * fix(providers): preserve reasoning_content in kimi tool loop
…imstudioai#5722) * feat(library): add best AI agent platforms 2026 comparison article * fix(library): count all eleven compared platforms and add Dust/Lindy table rows
…token-SA name-collision 409 (simstudioai#5693)
…imstudioai#5720) * feat(table): per-plan table dispatch concurrency with env overrides * fix(table): enforce shared concurrencyKey cap on database batchEnqueueAndWait * refactor(table): thread dispatch concurrency via invocation instead of persisting it * improvement(table): collapse dispatch concurrency env vars to FREE/PAID
… block double-covered personal checkouts (simstudioai#5715) * improvement(checkout): enforce team/enterprise-only org subscriptions, block double-covered personal checkouts, and drop renewal-triggered workspace detach * close race with personal pro / org inclusions * address comments * fix(billing): compute org coverage independently of the personal-sub lookup and fail closed on unverifiable plan writes
* feat(forking): excluded workflows * improve sync preview
…imstudioai#5728) * feat(storage): native Google Cloud Storage support for self-hosting Adds GCS as a third object-storage backend with full parity with S3 and Azure Blob: uploads, streaming downloads, deletes, head, V4 signed URLs (single + batch), and browser/server multipart uploads via the GCS XML API. Selection precedence is Azure Blob > S3 > GCS > local disk. - new provider client at lib/uploads/providers/gcs (cached singleton, ADC/Workload Identity or inline GCS_CREDENTIALS_JSON auth) - per-context GCS_*_BUCKET_NAME config wired through getStorageConfig - shared getServeStoragePrefix() replaces hardcoded blob/s3 serve paths - docs (object-storage, environment-variables), .env.example, helm values.yaml + values-gcp.yaml storage section * fix(storage): review round 1 — GCS per-context bucket fallback + ETag quote normalization - getGcsConfig falls back to the general bucket for every context (GCS bucket names are globally unique, so the S3-style sim-execution-files literal default would point at an unowned bucket; empty per-context buckets previously made uploads and downloads disagree) - completeGcsMultipartUpload restores quotes on ETags stripped by the shared browser upload client before building the completion XML - docs/.env.example/helm updated for the fallback behavior * fix(storage): review round 2 — route chat authz and execution-URL detection through getStorageConfig - getChatStorageConfig delegates to getStorageConfig('chat') (identical for S3/Azure, picks up the GCS general-bucket fallback instead of reading the raw chat config and rejecting valid chat files) - parse route resolves the execution bucket via getStorageConfig('execution') for all providers, so GCS execution files in the fallback bucket are still recognized as our own objects * fix(storage): validation pass — gcs serve-prefix parity in key parsers + CORS doc fix - extractStorageKey, extractFilename, and extractEmbeddedFileRef now strip the gcs/ serve prefix like s3/ and blob/, so direct-uploaded files on GCS parse, delete, download, and embed correctly (previously only the serve route knew the prefix) - file-download storageProvider union includes 'gcs' - completeGcsMultipartUpload defensively rejects a 200 response carrying an XML error document - docs: CORS example lists concrete x-goog-meta-* header names (GCS matches responseHeader entries exactly; wildcards are only supported for origin)
simstudioai#5729) * improvement(workflows): stop writing placeholder workflow descriptions * fix(workflows): scrub placeholder descriptions at the import boundary * test(workflows): pin import-boundary description scrubbing behavior
…oai#5731) * feat(landing): X pixel conversion tracking on landing pages * fix(landing): dedupe X pixel initial PageView by URL to survive Strict Mode effect replay * fix(landing): scope X pixel URL dedupe to the tracker instance so same-URL returns to landing still track
…nd output panel (simstudioai#5730) * improvement(workflow): zero-render drag-resize for panel, terminal, and output panel Port the sidebar's pointer-capture + rAF + CSS-variable drag pattern to use-panel-resize, use-terminal-resize, and use-output-panel-resize so a drag writes only --panel-width/--terminal-height/--output-panel-width per frame and commits to Zustand (one re-render + one localStorage write) on pointerup. Previously every mousemove dispatched a store set, re-rendering the whole always-mounted Panel tree (Chat/Editor/Toolbar) and the terminal, plus a persist localStorage write per move. Also drop the Panel's unused panelWidth subscription and drive the output panel width via a CSS variable instead of React state. * improvement(workflow): shared useDragResize hook + review fixes Extract the drag mechanism into a shared useDragResize hook (pointer capture, rAF-aligned apply, commit-on-release) consumed by the panel, terminal, and output-panel resize hooks. Fixes from adversarial review: commit the last computed value instead of reading the CSS var back (a fast single-frame flick could be lost to a cancelled rAF, and a pre-rehydration read returned '' -> NaN), floor the panel/terminal max clamp at the minimum so narrow viewports can't invert the clamp, guard pointerup/pointercancel by pointerId so a second touch pointer can't kill the drag, and capture the terminal rect once on drag start instead of per-frame getBoundingClientRect. Remove the now-dead isResizing store state and centralize CONTENT_WINDOW_GAP in stores/constants. * fix(workflow): compute drag value rAF-aligned from the latest pointer event Run compute inside the rAF (before the CSS-var write, so any layout read hits clean layout at most once per frame) and derive the final value from the latest pointer event on release. The output-panel hook now captures the terminal element on drag start and re-reads its rect per frame, so the clamp stays correct when the terminal resizes mid-drag and the live width can never exceed the current max. * fix(terminal): clamp output panel against the live CSS-var width The ResizeObserver clamp compared the persisted store width, which is intentionally stale during a drag; a terminal shrink mid-drag could overwrite the live width with a stale store value. Compare against the live --output-panel-width variable (store as pre-write fallback) so the clamp converges with the drag instead of fighting it.
…imstudioai#5734) * feat(chat): favicon external links with secure link-preview tooltips * fix(link-preview): address review findings - allow http fetches to match advertised http(s) link support - fix meta content regex to handle apostrophes and either quote delimiter - hash Redis cache keys so sensitive URLs are not stored verbatim - add per-user rate limit to the outbound-fetching route - render siteName-only previews instead of falling back to the URL * fix(link-preview): redact full URLs from failure logs * improvement(link-preview): render-time preview fetch, cheerio parsing, cleanup pass - fetch previews when links render (emcn tooltip shows instantly — hover prefetch had no delay to race); tooltip reads the warmed cache, eliminating the URL-then-preview flash - parse OG metadata with cheerio (already used server-side) instead of hand-rolled regexes + entity decoding, fixing double-decode and quote-handling classes - drop the no-longer-needed prefetch hook; remove dead side prop on Tooltip.Content - extract ExternalLink to a sibling module per component-size guidelines; fix TSDoc placement * fix(link-preview): https-only previews and full-document parsing - drop allowHttp: plain-http fetches would reach the URL validator's self-host loopback exception; previews are now explicitly https-only on both server (early null) and client (query never fires for http) - parse the full capped document instead of truncating at the first <body> substring, which could match inside head scripts/comments and drop metadata * improvement(chat): render mailto links as plain text
…#5786) * feat(copilot): add service_account_get_setup_link handler Resolves a loosely-specified integration name to the catalog slug whose detail page mounts ConnectServiceAccountModal, and returns `/integrations/{slug}?connect=service-account`. The agent surfaces it via the existing <credential type="link"> tag, so the user gets a Connect button and supplies the key material in Sim's own form — the agent never handles the secret. Exact matches beat fuzzy ones so a caller naming a specific service lands on it (gmail stays Gmail rather than collapsing to Drive), and family names resolve through an explicit canonical map rather than to whichever member sorts first. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Phx1MLjf8Ui3M3VpwisZds * fix(copilot): reject service account ids in oauth_get_auth_link The fuzzy provider match falls back to substring containment, so `slack-custom-bot` contains `slack` and resolved to the Slack OAuth service. The tool then returned a personal-OAuth authorize URL and reported success — a user who asked for a shared custom bot got a Connect button that linked their own account instead. Every service account id degraded this way (notion-, salesforce-, zoom-, linear-), always silently. Guard runs before the fuzzy pass and points at service_account_get_setup_link. Keys off the id being a service-account id, not off the integration offering one, so `slack` and `notion` still resolve for OAuth. Moves the narrowing predicate out of the integration catalog module so callers that need only the predicate skip the integrations.json load and the OAUTH_PROVIDERS walk. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Phx1MLjf8Ui3M3VpwisZds * feat(copilot): open the service account form in-chat instead of linking out The tool handed back a /integrations/{slug}?connect=service-account URL, so accepting the agent's offer navigated away from the conversation that asked for the credential. Adds a `service_account` credential tag that mounts ConnectServiceAccountModal over the chat; setup_url stays as the headless/MCP fallback. The tag carries a provider and no value — the secret is typed into Sim's own form and never enters the transcript — so the validator gets a branch alongside secret_input/sim_key rather than falling through to the value-required check. Extracts useServiceAccountConnectTarget so the chat and the integrations page share one source of truth for the connect label and the preview gate. Custom Slack bots ride the slack_v2 flag; without the shared gate the chat would have surfaced a setup form the integrations page hides. Modal is lazy-loaded off the deep path (not the barrel) to keep three provider-specific setup forms out of the chat's initial chunk. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Phx1MLjf8Ui3M3VpwisZds * fix(copilot): gate service account tool on the same preview flag as the UI The in-chat connect button hides itself when the provider's gating block is preview-hidden (a custom Slack bot needs slack_v2). The tool didn't check this, so it returned success for slack-custom-bot even when slack_v2 was preview-gated off — the agent said "here's the setup form" and the button silently rendered nothing, leaving the user with no form at all. Adds getServiceAccountGatingBlockType as the single source for the provider→gating-block mapping, consumed by both the tool (server-side, via getBlockVisibilityForCopilot) and the connect hook (client overlay). When the gating block is hidden the tool now fails with a fall-back-to-OAuth message instead of promising an invisible form. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Phx1MLjf8Ui3M3VpwisZds * feat(copilot): make the tool own service-account discovery Removes the VFS auth-metadata exposure and returns connectNoun from the service_account_get_setup_link result instead. The VFS aggregate was a second, viewer-independent source of truth that couldn't agree with the per-viewer preview gate (it always hid slack-custom-bot, even for viewers with slack_v2 revealed, while the tool accepts it for them). The tool now resolves the provider, applies the per-viewer gate, and returns either the in-chat button + connectNoun or a fall-back-to-oauth error — one source of truth. connectNoun stays DRY via getServiceAccountConnectNoun, shared with the connect-button label. * feat(copilot): make service-account setup a direct tag, no tool The agent now emits the service_account credential tag directly from intent — like secret_input — instead of round-tripping through a tool. Removes service_account_get_setup_link (handler, registration, display title, Go tool def) and restores auth.serviceAccount as the VFS discovery field so the agent knows which providers support a service account. The link-vs-tag distinction was the wrong axis: only oauth needs a tool, because its button carries a minted URL that can't be reconstructed. The service_account tag carries just a provider name the agent already knows, so it needs no tool — discovery lives in the VFS (auth.serviceAccount, GA-only, so slack's preview-gated custom bot is never proactively offered), and the per-viewer gate lives in the renderer, which renders nothing when a provider isn't available for the viewer (no OAuth fallback — a shared credential and a personal one are different intents). oauth_get_auth_link's service-account-id guard now points at the tag. * feat(copilot): support service-account reconnect from chat Reconnect had no service-account path — it required oauth_get_auth_link and a link tag for every repair, so rotating a workspace service account either errored or pushed the user through OAuth. The service_account tag now takes an optional credentialId; when present the renderer opens the modal in reconnect mode (rotates the secret on that credential in place, id preserved) and labels the button "Reconnect X". credentials.json now carries each credential's type (oauth vs service_account) so the agent can branch: service accounts reconnect via the tag + credentialId, oauth via oauth_get_auth_link as before. * fix(copilot): coherent service-account rejection in oauth_get_auth_link Review round on simstudioai#5786: - The service-account-id guard threw into the generic catch, which overwrote its recovery hint with a "connect manually" message and a workspace oauth_url — contradictory signals. It now returns a coherent failure directly, before the try, with no oauth_url. - Normalize spaces/underscores before the check so a readable form ("slack custom bot", "google service account") is caught too, not passed to the fuzzy OAuth resolver. - Remove listServiceAccountIntegrationNames — dead after the tool was removed (its only caller was the deleted handler's error copy). * fix(copilot): service-account discovery must un-gate after the block GAs Review round on simstudioai#5786: describeServiceAccountForOAuthProvider used `getBlock(...)?.preview ?? true`, which treats a GA'd gating block — one that dropped its `preview` flag, exactly slack_v2's documented migration — as still gated, so the custom bot would stay omitted from VFS discovery forever after GA even though the UI shows it. Reuse the canonical isHiddenUnder(null, block) predicate instead, so a non-preview block is visible. Adds service-account-gate.test.ts covering preview → omit, GA → include, and missing → fail-closed with a mocked getBlock (the block registry is globally stubbed, so the real slack_v2 preview flag isn't observable through serializeIntegrationSchema). * fix(copilot): align SA resolver normalization and reject blank credentialId Review round on simstudioai#5786: - resolveServiceAccountIntegration only lowercased/trimmed, but the oauth_get_auth_link guard normalizes spaces/underscores to hyphens before rejecting a service-account id and steering the agent to a service_account tag. The chat renderer then couldn't resolve those same readable forms ("slack custom bot", "notion_service_account") and rendered nothing. Apply the same normalization to the id lookups (raw query still used for display-name matches). - service_account tag validation rejected a blank provider but allowed a whitespace-only credentialId, which is truthy — the renderer took the reconnect path and tried to rotate a non-existent credential. Reject a blank/whitespace credentialId. * refactor(credentials): route the editor SA picker through the canonical connect hook The workflow-editor credential selector (from simstudioai#5800's merged picker) resolved its service-account setup surface inline and mounted the modal with NO preview gate — so a `credentialKind: 'service-account'` picker would offer a custom-bot setup even when slack_v2 is preview-gated off, the leak the integrations page and chat already guard against. Route it through the shared useServiceAccountConnectTarget hook (the same resolver chat and the integrations page use): suppress the setup action when `hidden`, and use the hook's vendor-accurate label ("Add private app token", "Set up a custom bot") as the default connect-row copy. Existing service accounts stay selectable; the per-block `credentialLabels.serviceAccountConnect` override still wins. One resolver now backs all three SA connect surfaces. * docs(add-block): document credentialKind and the service-account picker The add-block skill had no mention of credentialKind — the mechanism (simstudioai#5800) that controls whether an oauth-input offers OAuth, service-account, or a merged picker — and its example was a plain oauth-input mislabeled "Service Account". Documents the three credentialKind modes, that a default oauth-input already lets users select an existing service account (they fold in), and the credentialLabels / allowServiceAccounts companions. Regenerates the .claude and .cursor projections. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…chain mock (simstudioai#5856) * improvement(testing): consolidate @sim/db mocks into one table-aware chain mock - back databaseMock and dbChainMock with the SAME db instance so a module bound to either export hits identical chain fns — rival-mock divergence between the two @sim/testing db mocks is structurally impossible now - add queueTableRows(table, rows): FIFO per-table select routing keyed by schema-mock table identity, consumed at where() materialization and resolved by every downstream terminal (limit/orderBy/groupBy/for/joins) - delete createMockDb (duplicate chain implementation, no external users) - migrate the five suites that hand-rolled table routing + databaseMock delegation (billing plan/usage/usage-log, admin dashboard-organizations, workspaces/utils) onto queueTableRows; net -295 lines - add a contract test for the mock itself and a test script to @sim/testing so its tests actually run under turbo * fix(testing): harden table routing — join-table queues, direct-await from, mutation isolation - track the chain's tables as a list (from + joins) so rows queued for a join-only table route correctly; from-table queue checked first - make the from/join builder a lazy thenable so awaiting a select with no where clause resolves queued rows (dequeue at await, never double-consumed) - update/delete/set clear the routing context so a mutation's where() can never consume rows queued for a select - document the left-to-right chain-construction assumption; contract tests for all three behaviors * fix(testing): close routing over each chain's own tables for direct-await builders * refactor(testing): move all chain routing state into per-chain closures - shared dbChainMockFns entries become pure spy/override ports: their default implementation returns a sentinel that chain-local builders replace, while any mock* override on the spy wins verbatim - each select().from() captures its own immutable table list; where(), joins, terminals, and direct awaits all resolve through that closure, so partially-built chains for different tables interleave without cross-talk - no module-level routing state remains * fix(testing): lazy queue consumption at resolution and wrapper restore on reset - each chain holds one lazy rows supplier: the queued set is dequeued only when a default thenable actually resolves, so a chain answered by a per-test terminal override leaves its queued rows for the next chain - resetDbChainMock also mockReset()s the stable db entry-point wrappers so direct overrides on databaseMock.db.* cannot outlive a suite
…t at the lockfile key (simstudioai#5859) * fix(ci): save the Next.js build cache every run instead of freezing it at the lockfile key The cache key was only runner.os + bun.lock hash, and GitHub caches are immutable per key: the first run after a lockfile change saved the cache once, then every later run hit the primary key and skipped the save ('Cache hit occurred on the primary key ... not saving cache'), so builds compiled against a cache stale since the last lockfile bump. Suffix the key with the commit SHA so each run saves its refreshed cache, and restore via prefix match to the most recent entry. * fix(ci): make the Next.js cache key unique per run attempt so reruns can save too
…lient IP resolution (simstudioai#5857) * improvement(auth): bump better-auth to 1.6.23 and add trusted-proxy client IP resolution * chore(billing): record checkout-scope mirror re-verification against @better-auth/stripe 1.6.23 * chore(deploy): expose AUTH_TRUSTED_PROXIES in docker-compose.prod and Helm chart
…org, workflows/background (simstudioai#5861) * improvement(tests): migrate knowledge, billing/org, and workflows/background suites off private @sim/db factories * improvement(tests): db-mock migration tranche 1 — knowledge, billing/org, workflows/background - migrate 19 suites off private vi.mock('@sim/db') factories onto the shared dbChainMock + queueTableRows API (net ~-1,260 lines of bespoke chain plumbing); resolves the known shared-worker rival pairs (knowledge processing-queue vs api utils; billing polluters; persistence/utils vs schedules/deploy) - add .for() to the mock's limit builder (drizzle .limit(1).for('update')) with a contract test - document the join-table queue fallback footgun on queueTableRows
…, workspaces, connectors, mcp (simstudioai#5863) * improvement(tests): db-mock migration tranche 2 — copilot, mothership, workspaces, connectors, mcp * fix(testing): drain unconsumed ...Once overrides in resetDbChainMock vi.clearAllMocks clears call history only — a ...Once override queued by a previous test but never consumed survived into the next test. resetDbChainMock now mockReset()s every shared spy and stable wrapper, which restores the original implementation AND drains once-queues.
…copilot remainder, ee/core/misc (simstudioai#5864) * improvement(tests): db-mock migration tranche 3 — lib/workflows, lib/copilot remainder, ee/core/misc * improvement(tests): use the shared notLike operator in idempotency cleanup suite
…xecution/logs, routes/misc (final) (simstudioai#5866) * improvement(tests): db-mock migration tranche 4 — billing, webhooks/execution/logs, routes/misc (final) * fix(tests): route agent-handler MCP server rows through queueTableRows
…ock (simstudioai#5867) * feat(api): add proxyUrl for residential/custom proxy egress on the API block The HTTP/API block egresses from the app runtime's fixed datacenter IPs via secureFetchWithPinnedIP, so targets behind Cloudflare/WAF that block datacenter IPs (e.g. state .gov license portals) return 403/429 even when the identical request works from a browser. There was no way to route a request through a residential/custom proxy. Add an optional `proxyUrl` field (Advanced) to the API block. When set, the request routes through the given http:// proxy so it egresses from that proxy's IP. Security: - validateAndPinProxyUrl resolves the proxy host's DNS and blocks private/reserved/loopback IPs (same SSRF guard as target URLs), then pins the connection by rewriting the host to the resolved IP (creds/port preserved), closing the DNS-rebinding window. - Restricted to the http: proxy scheme (https/socks rejected) so host pinning is safe without breaking TLS-to-proxy SNI. - Target-IP pinning is intentionally bypassed when a proxy is active (the proxy resolves the target); target URL validation still runs. Threaded block field -> http tool param -> formatRequestParams -> executeToolRequest (validate + pin) -> secureFetchWithPinnedIP, which swaps its pinned Node agent for HttpsProxyAgent/HttpProxyAgent (keyed off target protocol) when proxyUrl is set. * docs(api): document the Proxy URL advanced field and steer proxy credentials to env vars * fix(api): reject loopback/private proxy hosts unconditionally, closing the self-hosted rebinding gap * chore(api): tighten proxy-path inline comments --------- Co-authored-by: Marcus Chandra <mzxchandra@gmail.com>
…ocation (simstudioai#5862) * feat(auth): org session policies — lifetime/idle limits, org-wide revocation, cookie-cache versioning * refactor(auth): consolidate session-policy clamp semantics, shared security-policy version module, canonical bounds, docs * polish(session-policy): cleanup pass — muted field labels, spinner reset, state tracker, response-seeded baseline, comment trims * fix(session-policy): govern member sessions by membership (closes revoke cookie-cache hole), normalize createdAt, remount on org switch, sync audit mock * fix(session-policy): clamp pre-join sessions on invite acceptance, normalize expiresAt, sync unified nav test * fix(session-policy): invalidate membership cache on removal/transfer, spare impersonator sessions in revoke-all, raise idle floor to 2x cookie window * fix(session-policy): resolve governing org by membership only — activeOrganizationId goes stale across transfer/leave * fix(session-policy): atomic policy save + eager clamp, asymmetric membership TTL, admin-add cache invalidation * fix(session-policy): org-scoped cookie version string, atomic revoke delete+bump * fix(session-policy): plan-gate effective policy so downgraded orgs stop enforcing automatically * chore(session-policy): drop dead bumpSecurityPolicyVersion helper — call sites bump transactionally * fix(session-policy): unify join paths on applySessionPolicyToNewMember; final audit polish (dead exports, response bound, test name)
…cky disk (simstudioai#5869) * feat(ci): warm Next.js builds via Turbopack persistent cache on a sticky disk - enable experimental.turbopackFileSystemCacheForBuild behind NEXT_TURBOPACK_BUILD_CACHE so only the CI check build opts in; production image builds stay on the default cold path until the feature stabilizes - mount ./apps/sim/.next/cache as a Blacksmith sticky disk (cache-mount) instead of actions/cache: the turbopack cache is ~5 GB, which a sticky disk mounts in ~1s while an actions/cache round-trip would eat the win - measured locally: 105s cold compile vs 22s warm (4.8x) * chore(ci): drop the superseded actions/cache comment and restore trailing newline
…ock (simstudioai#5871) * improvement(tests): converge env-flags mocks onto a complete shared mock * fix(tests): drop the repo's only bare vi.mock automock A bare vi.mock('drizzle-orm') automock colliding with factory mocks of the same module in a shared worker corrupts vitest's mock registry (upstream vitest-dev/vitest#10290 / #10145, reproduced in isolation). The global factory mock already covers this suite. * chore(tests): drop defensive resets in non-mutating suites, merge sequential setEnvFlags calls * fix(tests): run providers/utils cases sequentially over shared env-flags state
…nd CI runner-minute cuts (simstudioai#5875) * chore(ci): cut redundant runner minutes — dedup promotion-PR test runs, companion-pr-check concurrency, right-size trivial jobs - ci.yml: new dedup-promotion gate skips the pull_request test-build on staging/main-headed promotion PRs only when the merge tree provably equals the head tree (empty base delta over the merge base) AND the push-event run at the same sha passed its test jobs (polled). Fail-open on any error/ timeout/failure, job-level skip only (skipped job reports Success); verified no required status checks are configured on main/staging rulesets. Measured 39 duplicate PR runs / 5.15 days (~227/mo) at ~7.1 min each on 8vcpu (~57 vcpu-min), probe costs ~9 vcpu-min worst case on 2vcpu. - companion-pr-check.yml: per-PR concurrency group with cancel-in-progress so superseded synchronize/edit runs stop; no paths filter (check depends on PR body + cross-repo state, not changed files). - detect-version and check-docs-changes: 4vcpu -> 2vcpu Blacksmith runners (pure shell / depth-2 checkout + path filter only). * improvement(testing): complete stateful shared mocks for env, urls, redis-config, environment-utils Shared mock infrastructure for vitest isolate:false convergence: - packages/testing/src/mocks/env.mock.ts: stateful envMock (live env proxy, setEnv/resetEnvMock, process.env fallback) - packages/testing/src/mocks/urls.mock.ts: complete urlsMock with real-behavior default impls + resetUrlsMock - packages/testing/src/mocks/redis-config.mock.ts: adds getRedisConnectionDefaults + resetRedisConfigMock - packages/testing/src/mocks/environment-utils.mock.ts: new environmentUtilsMock + fns + reset - contract tests: env.mock.test.ts, urls.mock.test.ts, redis-config.mock.test.ts, environment-utils.mock.test.ts - packages/testing/src/mocks/index.ts: barrel exports - apps/sim/vitest.setup.ts: global installs for env, urls, redis, environment/utils - real-module tests unmocked: lib/core/config/env.test.ts, lib/core/config/redis.test.ts, lib/core/utils/urls.test.ts, tools/index.test.ts (urls) - stubEnv/process.env fallout migrated to setEnv: lib/webhooks/providers/{revenuecat,rootly,instantly}.test.ts, app/api/auth/oauth2/authorize/route.test.ts * improvement(tests): drop redundant local mocks in executor/tools/providers and misc dirs (shared-worker readiness) * improvement(tests): drop redundant local mocks in app routes (shared-worker readiness) * improvement(tests): drop redundant local mocks in lib (shared-worker readiness) * fix(ci+testing): live base-tip recheck before dedup skip; prod-aware urls mock fallbacks - the dedup gate re-verifies merge-tree equivalence against the LIVE base tip at decision time, closing the window where the base branch gains real commits during the poll (frozen BASE_SHA check alone was stale) - the urls mock's getBaseUrl protocol prefix and getBaseDomain parse fallback now follow the shared isProd flag, mirroring the real module * fix(ci+testing): fail-closed nojobs fallback in dedup gate; TLS-aware redis defaults mock - the dedup gate no longer infers coverage from overall run conclusion when no 'Test and Build /' jobs match — a renamed or skipped test job now runs the tests instead of skipping them - the shared getRedisConnectionDefaults mock mirrors the real TLS resolution (rediss:// to a raw IP requires REDIS_TLS_SERVERNAME and yields tls.servername) * fix(ci): keep polling while nested test jobs have not appeared yet An in-progress push run lists its reusable-workflow jobs only after the caller starts; nojobs is now terminal (fail closed) only once the run has completed without them.
…nstead of a blank popup (simstudioai#5874) * fix(mcp): bound and retry OAuth start so a transient stall recovers instead of a blank popup Empirically root-caused a blank/stuck authorize popup: the provider (planetscale) and our guarded OAuth fetch are both fast (120/120 legs clean from staging), and /oauth/start uses local AES encryption with no Redis lock in its path — so the intermittent hang is the same transient headers-then-stalled-body class we've documented for CDN-fronted MCP hosts (a per-connection stall a fresh attempt dodges), which /oauth/start had no server-side bound against. - Bound every /oauth/start step with the shared timedStep helper (extracted from the callback route, now used by both) + an entry log, so a stalled step surfaces as a labeled error instead of hanging the request (and the browser popup) to the client's 30s timeout. - Retry mcpAuthGuarded once on a bounded 12s timeout: a fresh attempt gets a fresh connection and recovers from the transient stall automatically (two 12s attempts stay under the client's 30s deadline). McpOauthRedirectRequired (the success signal) and DCR-unsupported errors are never retried. Adds OauthStepTimeoutError + makeTimedStep to the shared oauth barrel and test mocks. * fix(mcp): drop the unsafe OAuth-start retry; fail fast without error-logging success Review fixes on the bound+retry change: - Removed the mcpAuthGuarded auto-retry. timedStep can't cancel the loser, so a lingering first attempt shares this server's OAuth row and could overwrite the retry's PKCE verifier / state after the client already got the second authorize URL, breaking the callback. Recovery is now fail-fast (504) → the user re-clicks, which is a clean fresh flow (fresh connection dodges the transient stall) with no shared-state race. - Catch McpOauthRedirectRequired (the success signal) INSIDE the bounded step and return it as a value, so a successful authorize is no longer error-logged as 'OAuth step failed'. - Tighten step budgets (5s DB x3 + 12s auth = 27s) to stay under the client's 30s /oauth/start deadline. * fix(mcp): route all bounded-step timeouts to the 504 handler Move OauthStepTimeoutError handling to the outer catch so a DB-step timeout (loadServer/getOrCreateOauthRow/loadPreregisteredClient) returns the same fast 504 'try again' as the auth step, not a generic 500. Documents that the fresh retry is race-safe: the callback correlates on the state nonce, so a lingering timed-out attempt overwriting the row's state only yields a clean invalid_state on the user's fresh authorize URL — never silent corruption. * fix(mcp): bound the setOauthRowUser write too so no step escapes the budget The user-stamp write was the one DB op left unbounded on the start path; wrap it in timedStep(DB_STEP_MS) so every step stays inside the sub-30s budget and its timeout routes to the same 504. * fix(mcp): shrink OAuth-start step budgets to fit the 30s client deadline with 4 DB steps Bounding setOauthRowUser added a fourth possible DB step, so 4x5+12=32s exceeded the client's 30s /oauth/start abort. Lower DB steps to 4s and auth to 10s: 4x4+10=26s worst case, leaving margin for middleware/network. Comment corrected.
… test speedup, org session policies, proxy URL for API calls
…simstudioai#5881) * fix(ci): unblock the deploy chain — explicit need results on jobs downstream of test-build test-build's needs chain now contains dedup-promotion, which is skipped on every push event. A skipped transitive ancestor fails the implicit success() on downstream jobs, so migrate, promote-images, create-ghcr-manifests, process-docs, and create-release all cascade-skipped on push runs — blocking staging and main deploys (no ECR tag push, no CodeDeploy). Each of those jobs now uses !cancelled() plus explicit needs.<job>.result == 'success' checks, preserving their original semantics while ignoring the skipped ancestor. * chore(ci): remove the promotion-dedup gate — savings don't justify deploy-graph complexity The bespoke dedup job hand-rolled what content-addressed caching solves idiomatically, saved only ~$50-90/mo, and its needs edge just caused the deploy-chain skip incident. test-build returns to its original shape; the explicit need-result conditions on the deploy chain stay as hygiene.
…simstudioai#5880) * fix(confluence): exclude archived pages from KB connector listings so reconciliation purges them * fix(connectors): purge archived/deleted source items across seven more KB connectors The sync engine only purges a knowledge-base document when its source item is absent from a full-sync listing, so any connector that keeps listing archived/trashed/canceled items never drops them. An audit of all 51 connectors found seven with this bug: - asana: list only non-archived projects (the API returns both when `archived` is omitted), so tasks under archived projects stop being re-listed - google-sheets: skip a spreadsheet Drive reports as trashed, which stays readable by id for 30 days before the Sheets call starts 404ing - incidentio: exclude canceled incidents by default (cancelling is incident.io's documented stand-in for deletion), with an explicit opt-in to sync them - outlook: exclude Deleted Items from the all-mail listing, which Graph otherwise includes - servicenow: drop retired knowledge articles, which the Table API returns with no implicit state filter - webflow: drop archived CMS items, which the staged items endpoint always returns and offers no way to filter - youtube: drop playlist entries whose video was deleted or made private, which the API keeps returning as placeholder items Every exclusion keys off an explicit non-current signal and fails open on a missing field or a failed metadata read, since wrongly excluding a live item would hard-delete it. Explicit user filter selections are still honoured verbatim; the new defaults apply only when nothing is configured. Also flag truncated listings as capped in asana, outlook, and servicenow. All three silently cut a listing short at their configured item cap without setting `syncContext.listingCapped`, so reconciliation read the untraversed tail as deleted at the source and hard-deleted it. * fix(asana): honour the pinned-project exception on the task rehydrate path listDocuments deliberately keeps syncing a project the user pinned via the `project` config field even once it is archived, but getDocument ignored sourceConfig and applied the all-parents-archived exclusion unconditionally. For a pinned archived project the listing kept emitting its tasks while every hydration returned null, so new tasks were dropped as empty and already-indexed ones were frozen at their last content. isTaskUnderActiveProject now takes the pinned project gid and keeps any task reachable through it, matching the listing exactly. The unpinned path is unchanged and still fails open on missing/non-boolean archived values. * fix(connectors): key removal on explicit source signals, never on absence Follow-up to the connector purge fixes, from an independent audit. YouTube inferred deletion from absence: a playlist entry whose id was missing from a `videos.list` response was dropped, so a well-formed 200 that returned 49 of 50 requested ids hard-deleted the 50th. Playlist items instead carry a documented `status.privacyStatus`, available as a free part on a call the connector already makes, so the extra `videos.list` request is gone along with its quota-failure and pagination-wedge risks. An item is now excluded only on an explicit `private`; missing, empty, or unrecognized values keep it. ServiceNow read every record through a guard requiring a string `sys_id`, but the listing requests `sysparm_display_value=all`, under which every field — `sys_id` included — comes back as `{display_value, value}`. The guard rejected every record, so the retired-article filter was unreachable and the sys_id object would have leaked into `externalId` and `title` had it not been. Records are now read through the existing `rawValue` normalizer, which accepts both wire shapes, and the fixtures use the shape the API actually returns. Also: resolve the ServiceNow cap ambiguity with `X-Total-Count` so a table that ends exactly on a page boundary is not read as truncated; stop the Google Sheets comment claiming a purge the engine's zero-document guard prevents; and assert the Outlook junk-mail invariant instead of comparing a constant to itself. Document the behavior change: content archived, retired, or trashed at the source is now removed from the knowledge base, and restoring it re-ingests it.
…mstudioai#5883) * fix(knowledge): purge trashed Google Sheets tabs on a normal sync Trashing a spreadsheet made listDocuments return an empty listing, but the sync engine's zero-document guard skips deletion reconciliation whenever a listing comes back empty and documents already exist — it can't tell a genuinely empty source apart from a provider outage. For a single-spreadsheet connector, trashing its one source item empties the entire listing, so the guard always fired and the stale tabs never got cleaned up on a normal sync, contradicting the documented behavior. Add shouldSkipEmptyListing, mirroring shouldReconcileDeletions: a connector can now set syncContext.sourceConfirmedEmpty when it has positively confirmed the empty result against the source (not merely inferred it from an empty listing page), letting reconciliation proceed. The Google Sheets connector sets this flag when it confirms the spreadsheet is trashed via a direct Drive metadata lookup. No other connector sets it, so this doesn't change behavior anywhere else. * fix(knowledge): let sourceConfirmedEmpty also bypass the mass-deletion safety threshold The zero-document guard bypass alone wasn't enough: for a trashed spreadsheet with more than 5 tabs, reconciliation would proceed but the separate mass-deletion ratio guard (>50% deleted, >5 docs) still blocked the actual delete on a normal sync, requiring a forced full resync anyway. Extracted the ratio guard into exceedsDeletionSafetyThreshold, mirroring shouldSkipEmptyListing, so a connector's positive source confirmation bypasses both guards consistently.
utcarshsrivastava-collab
pushed a commit
that referenced
this pull request
Aug 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Upstream sync — 2026-08-06-5
Merges
simstudioai/sim@578d9ddcintoupstream-sync/2026-08-06T11-45-10.Sync range: 149 commit(s) since
9d23e25c(lastSyncedUpstreamSha).Stack
upstream-sync/2026-08-05T10-46-19upstream-sync/2026-08-06T10-38-40upstream-sync/2026-08-06T11-28-59upstream-sync/2026-08-06T11-45-10upstream-sync/2026-08-06T13-57-59Tip-only landing: merge the tip PR into the target branch, then close lower stack PRs as superseded.
Ledger
Verification
✅⚠️
bun run check·bun run lintCheck / lint are advisory. Test and full build are left to CI.
Advisory verification failed (check/lint). These do not block the sync.
bun run testand fullbun run buildare left to CI. Review and fix on the draft PR as needed.bun run check
✅ passed
bun run lint
❌ failed (advisory)
Agent usage
Usage (stack rollup)
parent-grill-analysis
claude-opus-5parent-finalize-plan
claude-opus-5child-db-schema-migrations
gpt-5.6-lunachild-copilot-generated-catalog
gpt-5.6-lunachild-copilot-chat-mothership
gpt-5.6-lunachild-billing-usage-tests
gpt-5.6-lunachild-uploads-storage-gcs
gpt-5.6-lunachild-auth-oauth-credentials
gpt-5.6-lunachild-providers-models-envkeys
gpt-5.6-lunachild-deploy-state-machine
gpt-5.6-lunachild-tools-executor
gpt-5.6-lunachild-branding-workspace-ui
gpt-5.6-lunachild-finalize-merge
gpt-5.6-lunaTotals
Cost by agent
Draft — tip-only landing: merge this tip into the target branch, then close lower stack PRs as superseded.