diff --git a/.agents/skills/design-taste-frontend/SKILL.md b/.agents/skills/design-taste-frontend/SKILL.md index f7f472a74e9..11ad6fa3e86 100644 --- a/.agents/skills/design-taste-frontend/SKILL.md +++ b/.agents/skills/design-taste-frontend/SKILL.md @@ -4,7 +4,7 @@ source: https://github.com/leonxlnx/taste-skill — skills/taste-skill/SKILL.md description: Anti-slop frontend skill for landing pages, portfolios, and redesigns. The agent reads the brief, infers the right design direction, and ships interfaces that do not look templated. Real design systems when applicable, audit-first on redesigns, strict pre-flight check. --- -> **In this repo:** Tailwind 3.4 (`apps/sim/tailwind.config.ts`); animation via `import { motion } from 'framer-motion'` (not `motion/react` — rewrite every `motion/react` import in the samples below); icons from `@sim/emcn/icons`; colors through the CSS-variable tokens in `.claude/rules/sim-styling.md` (no hardcoded `text-gray-*`/hex/`zinc` utilities, no paired `dark:` utilities). This note overrides any conflicting guidance or code sample anywhere in this file. +> **In this repo:** Tailwind 4 (CSS-first config in `apps/sim/app/_styles/globals.css`); animation via `import { motion } from 'framer-motion'` (not `motion/react` — rewrite every `motion/react` import in the samples below); icons from `@sim/emcn/icons`; colors through the CSS-variable tokens in `.claude/rules/sim-styling.md` (no hardcoded `text-gray-*`/hex/`zinc` utilities, no paired `dark:` utilities). This note overrides any conflicting guidance or code sample anywhere in this file. # tasteskill: Anti-Slop Frontend Skill diff --git a/.agents/skills/ship/SKILL.md b/.agents/skills/ship/SKILL.md index bb6ccff5bb8..6db24936127 100644 --- a/.agents/skills/ship/SKILL.md +++ b/.agents/skills/ship/SKILL.md @@ -37,10 +37,10 @@ When the user runs `/ship`: - `bun run check:migrations origin/staging` must pass (staging is the PR base). Do not silence a flagged statement with a `-- migration-safe:` annotation unless `/db-migrate` confirmed the old code no longer depends on it; otherwise split the destructive change into a later deploy. 6. **Run pre-ship checks** from the repo root before staging. This has two phases: first **regenerate** every committed artifact so generated files never drift into a CI failure (this is what catches things like `agent-stream-docs` going stale after a `models.ts` edit), then run the **full audit suite** CI's `Lint and Test` job enforces. Both phases parallelize — but only across commands that write **disjoint** outputs — and a bare `wait` swallows child exit codes, so both phases below explicitly collect each job's status and abort ship if any failed. - **Phase A — regenerate the always-in-repo committed artifacts (parallel), then let step 7 stage whatever changed.** Regenerate only the generators whose inputs live entirely in this repo and that any ordinary code change can drift — `agent-stream-docs:generate` (derives from the provider model registry) and `skills:sync` (derives from `.agents/skills/**`). They write disjoint trees (`apps/docs/…/agent.mdx` vs the `.claude`/`.cursor` command projections), so they parallelize safely, and each is idempotent (a no-op when already in sync): + **Phase A — regenerate the always-in-repo committed artifacts (parallel), then let step 7 stage whatever changed.** Regenerate only the generators whose inputs live entirely in this repo and that any ordinary code change can drift — `agent-stream-docs:generate` (derives from the provider model registry), `docs-manifest:generate` (derives from docs page paths), and `skills:sync` (derives from `.agents/skills/**`). They write disjoint outputs (`apps/docs/…/agent.mdx`, `apps/sim/lib/copilot/generated/docs-manifest.ts`, and `.claude/skills` links), so they parallelize safely, and each is idempotent (a no-op when already in sync): ```bash rm -f /tmp/ship-gen-results - for g in agent-stream-docs:generate skills:sync; do + for g in agent-stream-docs:generate docs-manifest:generate skills:sync; do ( bun run "$g" >"/tmp/ship-gen-${g//:/-}.log" 2>&1; echo "$? $g" >>/tmp/ship-gen-results ) & done wait @@ -66,6 +66,10 @@ When the user runs `/ship`: # Runs every audit CI runs, concurrently, and replays the output of any that fail. # The audit list is derived in scripts/run-audits.ts — do not hand-list audits here. bun run check:audits || { echo "❌ audit(s) failed — do not ship"; exit 1; } + # CI's "Verify docs manifest is in sync" step is not a `check:*` script, so the runner above + # does not cover it. (CI's "Security audit" `bun audit` step is `continue-on-error` — advisory + # only, not a gate — so it is deliberately not run here.) + bun run docs-manifest:check || { echo "❌ docs manifest out of sync — do not ship"; exit 1; } ``` If Phase A regenerated a file, its matching `:check` in Phase B now passes trivially — that parity is the point. Do not ship with any generator or audit failing; fix the cause (never silence it) and re-run. `check:migrations` and `type-check` are covered by steps 5 and CI respectively and are not repeated here. 7. **Stage and commit** the changes with the generated message — including any files Phase A regenerated in step 6 diff --git a/.claude/rules/sim-settings-pages.md b/.claude/rules/sim-settings-pages.md index 763803ed7db..9893028c352 100644 --- a/.claude/rules/sim-settings-pages.md +++ b/.claude/rules/sim-settings-pages.md @@ -113,7 +113,7 @@ Adding a new settings page: ## Text-scale tokens (no literal pixel sizes) Settings pages never use a literal `text-[Npx]` class — always the named Tailwind -scale token from `apps/sim/tailwind.config.ts`'s `fontSize` extension (`text-micro` +scale token from the `@theme` block in `apps/sim/app/_styles/globals.css` (`text-micro` 10px, `text-xs` 11px, `text-caption` 12px, `text-small` 13px, `text-sm` 14px [Tailwind default, unmodified], `text-base` 15px, `text-md` 16px, `text-lg` 18px [Tailwind default]). A literal size is either a straight rename to the equivalent diff --git a/.claude/rules/sim-styling.md b/.claude/rules/sim-styling.md index 3fd18753f46..51a882f8607 100644 --- a/.claude/rules/sim-styling.md +++ b/.claude/rules/sim-styling.md @@ -46,7 +46,7 @@ setWidth: (width) => { ## Text Scale -Custom font sizes (`apps/sim/tailwind.config.ts`): `text-micro`=10px, `text-xs`=11px, `text-caption`=12px, `text-small`=13px, `text-base`=15px. `text-sm` is Tailwind default 14px. Field titles use `text-small` (13px); hints/errors use `text-caption` (12px). +Custom font sizes (the `@theme` block in `apps/sim/app/_styles/globals.css`): `text-micro`=10px, `text-xs`=11px, `text-caption`=12px, `text-small`=13px, `text-base`=15px. `text-sm` is Tailwind default 14px. Field titles use `text-small` (13px); hints/errors use `text-caption` (12px). Icons default `size-[14px]`. Equal h/w → `size-*` (`size-[14px]`, `size-4`), never `h-N w-N`. diff --git a/.cursor/rules/sim-styling.mdc b/.cursor/rules/sim-styling.mdc index 8b407a204b7..e4688808503 100644 --- a/.cursor/rules/sim-styling.mdc +++ b/.cursor/rules/sim-styling.mdc @@ -40,7 +40,7 @@ setWidth: (width) => { ## Text Scale -Custom font sizes (`apps/sim/tailwind.config.ts`): `text-micro`=10px, `text-xs`=11px, `text-caption`=12px, `text-small`=13px, `text-base`=15px. `text-sm` is Tailwind default 14px. Field titles use `text-small` (13px); hints/errors use `text-caption` (12px). +Custom font sizes (the `@theme` block in `apps/sim/app/_styles/globals.css`): `text-micro`=10px, `text-xs`=11px, `text-caption`=12px, `text-small`=13px, `text-base`=15px. `text-sm` is Tailwind default 14px. Field titles use `text-small` (13px); hints/errors use `text-caption` (12px). Icons default `size-[14px]`. Equal h/w → `size-*` (`size-[14px]`, `size-4`), never `h-N w-N`. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 82f4b3299cc..2b611026e2a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -554,6 +554,11 @@ jobs: permissions: contents: read packages: write + # Every matrix leg evaluates the same guard against the same commit, so the + # value is identical whichever leg reports it last. attest-subjects needs it + # to tell "the guard withheld latest" from "the registry read was stale". + outputs: + latest_fresh: ${{ steps.guard.outputs.fresh }} strategy: matrix: include: @@ -611,6 +616,280 @@ jobs: "${IMAGE}:${SHA}-amd64" "${IMAGE}:${SHA}-arm64" fi + # Sign the published images and attach SLSA provenance and an SBOM to each. + # + # This runs after create-ghcr-manifests rather than inside the build because + # buildx's own provenance/sbom attestations stay off (see the note in + # .github/actions/docker-build): the extra manifests they add to an index + # break the `imagetools create` retagging that promote-images depends on. + # Attaching attestations here instead leaves the index itself untouched — they + # are stored as separate referrer manifests that point at it. + # + # Resolve the set of digests that actually got published, so the attestation + # job below covers every tag a customer can pull. + # + # A static list is not enough. `imagetools create` always writes an INDEX, so + # `:-amd64` is a single-entry index whose digest differs from the + # `:-amd64` manifest it wraps — attesting the manifest leaves the tag + # people actually pin unverifiable. Which tags exist also varies per run: + # version tags only on a release, and the latest tags only when the monotonic + # guard in create-ghcr-manifests passed. Resolving tag -> digest here and + # de-duplicating is what keeps the two in step without hardcoding that logic + # twice. + attest-subjects: + name: Resolve Attestation Subjects + runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }} + timeout-minutes: 10 + needs: [create-ghcr-manifests, detect-version] + if: >- + !cancelled() && + needs.create-ghcr-manifests.result == 'success' && + needs.detect-version.result == 'success' && + github.event_name == 'push' && github.ref == 'refs/heads/main' + permissions: + contents: read + packages: read + outputs: + subjects: ${{ steps.resolve.outputs.subjects }} + count: ${{ steps.resolve.outputs.count }} + steps: + - name: Login to GHCR + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Resolve published tags to digests + id: resolve + env: + IS_RELEASE: ${{ needs.detect-version.outputs.is_release }} + VERSION: ${{ needs.detect-version.outputs.version }} + SHA: ${{ github.sha }} + LATEST_FRESH: ${{ needs.create-ghcr-manifests.outputs.latest_fresh }} + run: | + set -euo pipefail + + IMAGES="simstudio migrations realtime pii cron" + + # Prints the digest, or nothing when the tag is genuinely absent. + # + # An absent tag and a registry hiccup both make `inspect` fail, and + # treating them alike is how a published image silently ends up + # unsigned while this job still goes green. So: retry, and only report + # "absent" when the registry actually says the manifest is unknown. + # Anything else fails the step. + digest_of() { + local ref="$1" attempt raw err absent=0 + for attempt in 1 2 3; do + if raw="$(docker buildx imagetools inspect "$ref" --format '{{json .Manifest}}' 2>/tmp/inspect.err)"; then + printf '%s' "$raw" | jq -r '.digest // empty' + return 0 + fi + err="$(cat /tmp/inspect.err)" + # Absence is retried like any other failure: GHCR can report a + # just-published alias as unknown for a moment, and accepting that + # on the first attempt would skip a tag this run did publish. + case "$err" in + *"not found"*|*MANIFEST_UNKNOWN*|*"no such manifest"*|*"NAME_UNKNOWN"*) absent=1 ;; + *) absent=0 ;; + esac + if [ "$attempt" -lt 3 ]; then + sleep "$((attempt * 3))" + fi + done + # Only call it absent if the registry said so on the final attempt. + if [ "$absent" -eq 1 ]; then + return 0 + fi + echo "::error::Could not inspect ${ref} after 3 attempts: ${err}" >&2 + return 1 + } + + # Records a subject. `platform` tells the attestation job whether this + # digest is a single-architecture image, which is the only case where + # a Syft SBOM describes what the puller actually gets. + emit() { + jq -nc --arg image "$1" --arg digest "$2" --arg platform "$3" \ + '{image: $image, digest: $digest, platform: $platform}' >> /tmp/subjects.jsonl + } + + : > /tmp/subjects.jsonl + for name in $IMAGES; do + image="ghcr.io/simstudioai/${name}" + + # The sha tags are this run's own output. All three must resolve — + # a missing one means the publish did not complete, not that the tag + # is optional. + seen="" + sha_index="" + for tag in "${SHA}" "${SHA}-amd64" "${SHA}-arm64"; do + digest="$(digest_of "${image}:${tag}")" + if [ -z "$digest" ]; then + echo "::error::${image}:${tag} was not published by this run" + exit 1 + fi + case "$tag" in + *-amd64) platform=amd64 ;; + *-arm64) platform=arm64 ;; + *) platform=index; sha_index="$digest" ;; + esac + seen="$seen $digest" + emit "$image" "$digest" "$platform" + done + + # A moving alias is only taken when it resolves to the same index + # digest this run published — content identity, which is what a + # digest can prove. create-ghcr-manifests holds the latest tags back when + # its monotonic guard sees a newer commit, and they then still point + # at an older build — attesting those would put this run's signature + # and provenance on an image it did not produce. The per-arch + # aliases are published in the same guarded block as `latest`, so + # that one comparison gates all three. + alias_groups="latest" + if [ "${IS_RELEASE}" = "true" ]; then + alias_groups="${alias_groups} ${VERSION}" + fi + + for alias in $alias_groups; do + # A mismatch has two very different causes: the guard deliberately + # held the tag back, or GHCR is still serving the previous digest + # moments after this run wrote it. Re-read before concluding the + # former, or a read landing a second early silently drops three + # subjects from the matrix. + alias_index="" + for alias_attempt in 1 2 3; do + alias_index="$(digest_of "${image}:${alias}")" + [ "$alias_index" = "$sha_index" ] && break + [ "$alias_attempt" -lt 3 ] && sleep 5 || true + done + + if [ "$alias_index" != "$sha_index" ]; then + # `latest` is allowed to lag, but only when the guard actually + # withheld it. If the guard published latest this run, a mismatch + # here is a stale read, not a deliberate skip — and silently + # dropping it would leave a published tag unsigned. + if [ "$alias" = "latest" ]; then + if [ "${LATEST_FRESH}" = "true" ]; then + echo "::error::${image}:latest was published by this run but resolves to ${alias_index:-nothing}" + exit 1 + fi + echo "Skipping latest* for ${image}: the monotonic guard withheld it this run." + continue + fi + # A version tag has no such carve-out. This run published it, so + # a release must not ship a version image nothing has attested. + echo "::error::${image}:${alias} does not resolve to this run's index (${sha_index:-none}); refusing to publish an unattested release image" + exit 1 + fi + for tag in "${alias}" "${alias}-amd64" "${alias}-arm64"; do + digest="$(digest_of "${image}:${tag}")" + if [ -z "$digest" ]; then + echo "::error::${image}:${tag} is missing though ${image}:${alias} is current" + exit 1 + fi + case " $seen " in *" $digest "*) continue ;; esac + seen="$seen $digest" + case "$tag" in + *-amd64) emit "$image" "$digest" amd64 ;; + *-arm64) emit "$image" "$digest" arm64 ;; + *) emit "$image" "$digest" index ;; + esac + done + done + done + + if [ ! -s /tmp/subjects.jsonl ]; then + echo "::error::Resolved no image digests to attest" + exit 1 + fi + + echo "Resolved $(wc -l < /tmp/subjects.jsonl) distinct subjects:" + cat /tmp/subjects.jsonl + echo "subjects=$(jq -sc . /tmp/subjects.jsonl)" >> "$GITHUB_OUTPUT" + echo "count=$(wc -l < /tmp/subjects.jsonl | tr -d ' ')" >> "$GITHUB_OUTPUT" + + # One leg per distinct published digest. Attesting each subject separately is + # also what makes the SBOMs truthful: the amd64 and arm64 images contain + # different packages, and one SBOM attached to the index cannot describe both. + attest-images: + name: Attest Images + runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }} + timeout-minutes: 15 + needs: [attest-subjects] + if: >- + !cancelled() && + needs.attest-subjects.result == 'success' + permissions: + contents: read + packages: write + # Sigstore signs against the runner's OIDC identity; no key material is stored. + id-token: write + attestations: write + strategy: + fail-fast: false + matrix: + include: ${{ fromJSON(needs.attest-subjects.outputs.subjects) }} + + steps: + - name: Login to GHCR + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Skipped for index subjects: Syft resolves an index to one platform, so + # the SBOM it produces would describe amd64 while the index also serves + # arm64. The per-arch subjects below carry an accurate SBOM each, and the + # index still gets a signature and provenance. + # + # Scanned by the `-` tag rather than by `matrix.digest`. Half + # the per-arch subjects are single-entry INDEXES (`imagetools create` + # writes an index even from one manifest), and Syft resolves an index + # against the RUNNER's platform — so an arm64-only index fails outright on + # an amd64 runner with "no child with platform linux/arm64". The sha tag is + # the plain manifest that index wraps: identical content, no platform + # resolution, and one pull shared by both subjects instead of two. + - name: Generate SBOM + if: matrix.platform != 'index' + uses: anchore/sbom-action@3ad7283483fc7af8ff2b4ea19663c2d5ca935e26 # v0.24.2 + with: + image: ${{ matrix.image }}:${{ github.sha }}-${{ matrix.platform }} + format: spdx-json + output-file: sbom.spdx.json + # The action's own release upload is for workflows triggered by a + # release; these attach to the image instead. + upload-artifact: false + upload-release-assets: false + + - name: Attest SBOM + if: matrix.platform != 'index' + uses: actions/attest-sbom@c604332985a26aa8cf1bdc465b92731239ec6b9e # v4.1.0 + with: + subject-name: ${{ matrix.image }} + subject-digest: ${{ matrix.digest }} + sbom-path: sbom.spdx.json + # Stored alongside the image so a mirrored registry carries the + # attestation with it, rather than only being retrievable from GitHub. + push-to-registry: true + + - name: Attest build provenance + uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 + with: + subject-name: ${{ matrix.image }} + subject-digest: ${{ matrix.digest }} + push-to-registry: true + + - name: Install Cosign + uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 + + # The attestations above prove how the image was built; this is the plain + # signature that admission controllers (Kyverno, the Sigstore policy + # controller) verify before admitting a pod. + - name: Sign image + run: cosign sign --yes "${{ matrix.image }}@${{ matrix.digest }}" + # Check if docs changed # Smallest runner on purpose: a depth-2 checkout plus a path filter, no # install and no build. @@ -652,11 +931,22 @@ jobs: name: Create GitHub Release runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }} timeout-minutes: 10 - needs: [create-ghcr-manifests, detect-version] - # Explicit results: see migrate's comment. + needs: [create-ghcr-manifests, attest-subjects, attest-images, detect-version] + # Explicit results: see migrate's comment. attest-images is a gate, not just + # an ordering edge — a release must not advertise images whose signature or + # attestation failed to publish. The count check is belt and braces: today + # attest-subjects already fails on an empty subject set, so this only bites + # if that guard is ever removed. + # + # Note this gates the GitHub release, not the production deploy: CodePipeline + # fires from the ECR tags moved by promote-images, upstream of this job, and + # only the GHCR mirrors are attested. if: >- !cancelled() && needs.create-ghcr-manifests.result == 'success' && + needs.attest-subjects.result == 'success' && + needs.attest-subjects.outputs.count != '0' && + needs.attest-images.result == 'success' && needs.detect-version.result == 'success' && needs.detect-version.outputs.is_release == 'true' permissions: diff --git a/.github/workflows/desktop-release.yml b/.github/workflows/desktop-release.yml index fd9d8584687..f7b6e2c2dd9 100644 --- a/.github/workflows/desktop-release.yml +++ b/.github/workflows/desktop-release.yml @@ -187,12 +187,47 @@ jobs: printf '%s' "$APPLE_API_KEY_P8" > "$RUNNER_TEMP/appstoreconnect/AuthKey.p8" chmod 600 "$RUNNER_TEMP/appstoreconnect/AuthKey.p8" - - name: Package, sign, and notarize + - name: Import Apple signing certificate if: ${{ inputs.sign }} - working-directory: apps/desktop env: CSC_LINK: ${{ secrets.CSC_LINK }} CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} + run: | + CERTIFICATE_PATH="$RUNNER_TEMP/desktop-signing.p12" + KEYCHAIN_PATH="$RUNNER_TEMP/desktop-signing.keychain-db" + KEYCHAIN_PASSWORD="$(openssl rand -base64 32)" + + printf '%s' "$CSC_LINK" | base64 --decode > "$CERTIFICATE_PATH" + chmod 600 "$CERTIFICATE_PATH" + security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" + security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security import "$CERTIFICATE_PATH" -k "$KEYCHAIN_PATH" -P "$CSC_KEY_PASSWORD" \ + -T /usr/bin/codesign -T /usr/bin/productbuild + security set-key-partition-list -S apple-tool:,apple: -s \ + -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" > /dev/null + + EXISTING_KEYCHAINS=("$KEYCHAIN_PATH") + while IFS= read -r EXISTING_KEYCHAIN; do + EXISTING_KEYCHAIN="${EXISTING_KEYCHAIN#*\"}" + EXISTING_KEYCHAIN="${EXISTING_KEYCHAIN%\"}" + if [ "$EXISTING_KEYCHAIN" != "$KEYCHAIN_PATH" ]; then + EXISTING_KEYCHAINS+=("$EXISTING_KEYCHAIN") + fi + done < <(security list-keychains -d user) + security list-keychains -d user -s "${EXISTING_KEYCHAINS[@]}" + + SIGNING_IDENTITIES="$(security find-identity -v -p codesigning "$KEYCHAIN_PATH")" + if ! grep -q 'Developer ID Application' <<< "$SIGNING_IDENTITIES"; then + echo '::error::The signing certificate does not contain a valid Developer ID Application identity.' + exit 1 + fi + + - name: Package, sign, and notarize + if: ${{ inputs.sign }} + working-directory: apps/desktop + env: + CSC_KEYCHAIN: ${{ runner.temp }}/desktop-signing.keychain-db # Absolute path — @electron/notarize reads this via Node fs, which # does not expand a leading '~'. APPLE_API_KEY: ${{ runner.temp }}/appstoreconnect/AuthKey.p8 @@ -205,6 +240,13 @@ jobs: bunx electron-builder --mac --publish never -c.productName="$PRODUCT_NAME" -c.appId="$APP_ID" + - name: Remove Apple signing credentials + if: ${{ always() && inputs.sign }} + run: | + security delete-keychain "$RUNNER_TEMP/desktop-signing.keychain-db" || true + rm -f "$RUNNER_TEMP/desktop-signing.p12" + rm -f "$RUNNER_TEMP/appstoreconnect/AuthKey.p8" + # Unsigned artifact-only path: no Developer ID or notarization. The bundle # is ad-hoc signed with Hardened Runtime off for local workflow testing and # must never be published. diff --git a/.github/workflows/helm.yml b/.github/workflows/helm.yml index 05e2ee8a12b..6116c9ba1cb 100644 --- a/.github/workflows/helm.yml +++ b/.github/workflows/helm.yml @@ -6,11 +6,19 @@ on: paths: - 'helm/sim/**' - '.github/workflows/helm.yml' + # The image inventory is generated from the chart and checked here, so a + # change to its generator has to run this workflow too. + - 'scripts/generate-image-manifest.ts' + - 'package.json' pull_request: branches: [main, staging, dev] paths: - 'helm/sim/**' - '.github/workflows/helm.yml' + # The image inventory is generated from the chart and checked here, so a + # change to its generator has to run this workflow too. + - 'scripts/generate-image-manifest.ts' + - 'package.json' concurrency: group: helm-${{ github.ref }} @@ -43,6 +51,13 @@ jobs: - name: Scheduler parity (docker/crontab vs helm cronjobs) run: bun run scripts/check-cron-parity.ts + # helm/sim/images.yaml is what an operator mirrors into a disconnected + # registry, so a chart change that adds an image has to update it. Lives + # here rather than in `check:audits` because it renders the chart, and the + # audits job has no Helm. + - name: Image inventory is current + run: bun run images:check + - name: Helm lint run: helm lint helm/sim --values helm/sim/ci/default-values.yaml diff --git a/apps/docs/app/global.css b/apps/docs/app/global.css index aa2e6f740c8..b0f169b33fb 100644 --- a/apps/docs/app/global.css +++ b/apps/docs/app/global.css @@ -7,7 +7,7 @@ /* Every @sim/emcn component expresses hover through `hover-hover:` so touch devices never latch a sticky hover state. The app registers it as a plugin - variant in apps/sim/tailwind.config.ts; docs is CSS-first Tailwind v4 with no + variant in apps/sim/app/_styles/globals.css; docs is CSS-first Tailwind v4 with no config, so without this declaration the variant compiles to nothing and every emcn hover state silently no-ops here. */ @custom-variant hover-hover { @@ -35,7 +35,7 @@ body { --color-fd-primary: var(--color-fd-foreground); /* Sim's custom type scale — emcn components (Label, Badge, the shared block views) use these names, so they must resolve here or text falls back to the - inherited size. Mirrors apps/sim/tailwind.config.ts. */ + inherited size. Mirrors apps/sim/app/_styles/globals.css. */ --text-micro: 10px; --text-xs: 11px; --text-caption: 12px; @@ -47,7 +47,7 @@ body { --text-caption and --text-small, because the mono face reads small at 12px. */ --text-code: 0.78125rem; - /* The platform's code face. `apps/sim/tailwind.config.ts` lists `var(--font-martian-mono)` + /* The platform's code face. `apps/sim/app/_styles/globals.css` lists `var(--font-martian-mono)` first, but nothing in the app ever defines that variable — `apps/sim/app/layout.tsx` applies only `season.variable` — so every code surface in the product resolves to the system stack below. Docs match what the app actually renders rather than the token it diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index 0c0c8783e1b..23ddfd3d0ce 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -3196,6 +3196,52 @@ export function LemlistIcon(props: SVGProps) { ) } +/** + * The four brand fills are hardcoded rather than `currentColor`: this is a + * multi-color mark, so it stays legible on the white tile and bare on a neutral + * page in both themes. No `iconColor` is set for the same reason — there is no + * single brand tint to adopt. + * + * The source SVG carried its fills through `.st0`–`.st3` CSS classes; those are + * inlined here so the mark cannot depend on (or leak) global styles. + */ +export function ManageEngineIcon(props: SVGProps) { + return ( + + ) +} + export function TelegramIcon(props: SVGProps) { return ( = { mailchimp: MailchimpIcon, mailgun: MailgunIcon, managed_agent: ClaudeIcon, + manageengine_sdp: ManageEngineIcon, mem0: Mem0Icon, memory: BrainIcon, microsoft_ad: AzureIcon, diff --git a/apps/docs/content/docs/cli/configuration.mdx b/apps/docs/content/docs/cli/configuration.mdx index b5ba49ac7d4..8758ab51859 100644 --- a/apps/docs/content/docs/cli/configuration.mdx +++ b/apps/docs/content/docs/cli/configuration.mdx @@ -114,15 +114,80 @@ shared profile cannot also set its own endpoint or API key. | `SIM_API_KEY` | API key — skips `sim login` entirely | | `SIM_WORKSPACE` | Workspace to target | | `SIM_OUTPUT` | Output format | -| `SIM_CONFIG_DIR` | Relocate both files away from `~/.sim` | +| `SIM_CONFIG_DIR` | Relocate the config directory and update cache; file-specific overrides below still win | | `SIM_CONFIG_FILE` | Relocate only the config file | | `SIM_CREDENTIALS_FILE` | Relocate only the credentials file | | `SIM_TIMEOUT_SECONDS` | Per-request timeout; `0` waits indefinitely. Defaults to `3600`, above every timeout the server itself applies | | `SIM_DEBUG` | Trace each request's method, URL, status and duration to stderr | +| `SIM_NO_UPDATE_CHECK` | Turn off update checks | -Node ignores `HTTPS_PROXY` unless you also set `NODE_USE_ENV_PROXY=1`, and only -from Node 22.21 and 24.5. The CLI warns when a proxy is configured but will not -be used. +## Update notices + +On eligible invocations, the CLI uses a daily cache before asking +`registry.npmjs.org` what is published under the `latest` tag. Prerelease +installs are skipped entirely, so a `-preview` or `-dev` build is never told to +upgrade. When a newer one exists, it prints a single line on stderr naming both +versions and the command that upgrades: + +``` +Update available: sim 2.1.2 → 2.1.5. Run: npm install -g sim@latest +``` + +Apart from the configured registry URL, the request identifies only the CLI +version — no Sim API key, workspace, or command — and it never follows a +redirect away from the registry it asked. + +One caveat worth stating plainly: if you point `npm_config_registry` at a +private mirror, the check goes to that mirror instead of npm. Query-string +credentials (an Artifactory or Nexus `?token=…`, for example) are preserved and +sent as part of the configured registry request — they have to be, or the +mirror would reject it. As with other registry traffic, configured proxies or +TLS inspection can observe what that network setup permits. A registry URL +containing username/password userinfo, such as +`https://user:password@registry.example`, is rejected and no update check is +made. + +An empty or whitespace-only `npm_config_registry` is treated as unset, so the +public registry remains the default. Non-empty malformed and non-HTTP(S) values +disable the update check rather than making an unexpected public request. + +The notice is skipped entirely when: + +- `SIM_NO_UPDATE_CHECK` is set to anything but `0` or `false` +- stderr is not a terminal, so redirected and piped output is never affected +- a CI environment variable is present (`CI`, `GITHUB_ACTIONS`, `JENKINS_URL`, + `TEAMCITY_VERSION`, `BUILDKITE`) +- the CLI is running under `npm exec` or `npx`, which may use a project-local or + ephemeral package where global-install advice is inappropriate +- the CLI is running from a checkout of the sim repository, whose version + deliberately trails the published one +- the installed version is a prerelease + +The daily pace comes from a timestamp in the config directory's +`update-check.json`: `~/.sim/update-check.json` by default, or under +`SIM_CONFIG_DIR` when that is set. `SIM_CONFIG_FILE` and +`SIM_CREDENTIALS_FILE` do not move the cache, so it may not sit beside a file +relocated with either of those variables. + +This throttle is best-effort across processes. Two commands that start together +can both see a stale cache and check. Cache replacement is atomic, so either +complete write can win without leaving a partially interleaved file. If the +cache cannot be written — for example, because the config directory is +read-only — every eligible invocation attempts a check because there is no +timestamp to reuse. + +The registry check has a one-second deadline. On expiry, the CLI terminates its +short-lived request process so stalled DNS, connection, or response work cannot +remain active and delay the command. `SIM_NO_UPDATE_CHECK=1` still turns the +check off. + +The command the notice prints matches how Sim was installed — `npm install -g`, +`pnpm add -g`, `bun add -g`, or `yarn global add` — so running it updates the +executable already on your `PATH` rather than installing a second copy under a +different package manager. + +Node's `fetch` uses `HTTP(S)_PROXY` when opted in with `NODE_USE_ENV_PROXY=1` +(Node 22.21+ or 24.0+) or `--use-env-proxy` (Node 22.21+ or 24.5+). For CI, set `SIM_API_KEY` and `SIM_WORKSPACE` and nothing needs to touch the filesystem at all. diff --git a/apps/docs/content/docs/cli/files.mdx b/apps/docs/content/docs/cli/files.mdx index 3d04694e7b8..be9e2330c84 100644 --- a/apps/docs/content/docs/cli/files.mdx +++ b/apps/docs/content/docs/cli/files.mdx @@ -108,6 +108,8 @@ Also available as `sim files folders ls`. | `--sort-by ` | No | Field used to sort the result. Sorting by `name` is case-sensitive and follows the storage collation, so do not rely on a case-insensitive order. Accepted values: `name`, `createdAt`, `updatedAt`. | | `--sort-order ` | No | Sort direction. Accepted values: `asc`, `desc`. | | `--scope ` | No | Which lifecycle set to list: `active` (default) returns live folders only; `archived` returns folders a recursive delete soft-deleted, which is how a caller finds a path to hand to the folder restore. Authorization is identical for both. Accepted values: `active`, `archived`. | +| `--recursive ` | No | Whether parentPath includes every descendant instead of direct children only. Accepted values: `true`, `1`, `yes`, `on`, `y`, `enabled`, `false`, `0`, `no`, `off`, `n`, `disabled`. | +| `--depth ` | No | Deepest level below parentPath to include when recursive is true. | @@ -172,6 +174,32 @@ sim files delete [options] +## Apply one exact or anchor-based edit to a text file + +```bash +sim files edit [options] +``` + +**Arguments** + + + +| Argument | Required | Description | +| --- | --- | --- | +| `fileId` | Yes | File identifier. | + + + +**Options** + + + +| Option | Required | Description | +| --- | --- | --- | +| `--edit ` | Yes | One edit object: {"mode":"search_replace","search":"old","content":"new","replaceAll":false}, {"mode":"replace_between","beforeAnchor":"start line","afterAnchor":"end line","content":"new"}, {"mode":"insert_after","anchor":"line","content":"new"}, or {"mode":"delete_between","startAnchor":"first line deleted","endAnchor":"ending line kept"}. Anchored modes also accept occurrence starting at 1 (JSON, or @path / @- to read a file or stdin). | + + + ## Show file metadata and sharing status ```bash @@ -310,6 +338,8 @@ sim files read [options] | Option | Required | Description | | --- | --- | --- | | `--max-bytes ` | No | Optional ceiling on the source bytes fed to the parser, lowering but never raising the server limit. | +| `--offset ` | No | First line to return, 1-based. Absent starts at the first line. | +| `--limit ` | No | How many lines to return from `offset`. Absent reads to the end. | @@ -355,6 +385,27 @@ sim files restore +## Search file content + +```bash +sim files search [options] +``` + +**Options** + + + +| Option | Required | Description | +| --- | --- | --- | +| `--query ` | Yes | Regular expression, or exact text when `mode` is `exact`. | +| `--mode ` | No | How `query` is read. Accepted values: `exact`, `regex`. | +| `--max-results ` | No | Maximum matching lines to return. | +| `--folder ` | No | Folders to search, by path as shown in the app; omit to search the whole workspace (space-separated, or @path / @- with one value per line; @@value for a literal leading @). | +| `--include-subfolders` | No | Whether each folder scope includes nested folders; on by default. | +| `--no-include-subfolders` | No | Send --include-subfolders as false. | + + + ## Unzip an archive into a new folder beside it ```bash diff --git a/apps/docs/content/docs/cli/reference.mdx b/apps/docs/content/docs/cli/reference.mdx index 2aacf33181f..b6c6cc0b2e5 100644 --- a/apps/docs/content/docs/cli/reference.mdx +++ b/apps/docs/content/docs/cli/reference.mdx @@ -788,6 +788,8 @@ Also available as `sim files folders ls`. | `--sort-by ` | No | Field used to sort the result. Sorting by `name` is case-sensitive and follows the storage collation, so do not rely on a case-insensitive order. Accepted values: `name`, `createdAt`, `updatedAt`. | | `--sort-order ` | No | Sort direction. Accepted values: `asc`, `desc`. | | `--scope ` | No | Which lifecycle set to list: `active` (default) returns live folders only; `archived` returns folders a recursive delete soft-deleted, which is how a caller finds a path to hand to the folder restore. Authorization is identical for both. Accepted values: `active`, `archived`. | +| `--recursive ` | No | Whether parentPath includes every descendant instead of direct children only. Accepted values: `true`, `1`, `yes`, `on`, `y`, `enabled`, `false`, `0`, `no`, `off`, `n`, `disabled`. | +| `--depth ` | No | Deepest level below parentPath to include when recursive is true. | @@ -858,6 +860,34 @@ sim files delete [options] +### sim files edit + +Apply one exact or anchor-based edit to a text file + +```bash +sim files edit [options] +``` + +**Arguments** + + + +| Argument | Required | Description | +| --- | --- | --- | +| `fileId` | Yes | File identifier. | + + + +**Options** + + + +| Option | Required | Description | +| --- | --- | --- | +| `--edit ` | Yes | One edit object: {"mode":"search_replace","search":"old","content":"new","replaceAll":false}, {"mode":"replace_between","beforeAnchor":"start line","afterAnchor":"end line","content":"new"}, {"mode":"insert_after","anchor":"line","content":"new"}, or {"mode":"delete_between","startAnchor":"first line deleted","endAnchor":"ending line kept"}. Anchored modes also accept occurrence starting at 1 (JSON, or @path / @- to read a file or stdin). | + + + ### sim files describe Show file metadata and sharing status @@ -1006,6 +1036,8 @@ sim files read [options] | Option | Required | Description | | --- | --- | --- | | `--max-bytes ` | No | Optional ceiling on the source bytes fed to the parser, lowering but never raising the server limit. | +| `--offset ` | No | First line to return, 1-based. Absent starts at the first line. | +| `--limit ` | No | How many lines to return from `offset`. Absent reads to the end. | @@ -1055,6 +1087,29 @@ sim files restore +### sim files search + +Search File Content + +```bash +sim files search [options] +``` + +**Options** + + + +| Option | Required | Description | +| --- | --- | --- | +| `--query ` | Yes | Regular expression, or exact text when `mode` is `exact`. | +| `--mode ` | No | How `query` is read. Accepted values: `exact`, `regex`. | +| `--max-results ` | No | Maximum matching lines to return. | +| `--folder ` | No | Folders to search, by path as shown in the app; omit to search the whole workspace (space-separated, or @path / @- with one value per line; @@value for a literal leading @). | +| `--include-subfolders` | No | Whether each folder scope includes nested folders; on by default. | +| `--no-include-subfolders` | No | Send --include-subfolders as false. | + + + ### sim files unzip Unzip an archive into a new folder beside it diff --git a/apps/docs/content/docs/cli/troubleshooting.mdx b/apps/docs/content/docs/cli/troubleshooting.mdx index 8bf2dfd58c0..ae396ec325a 100644 --- a/apps/docs/content/docs/cli/troubleshooting.mdx +++ b/apps/docs/content/docs/cli/troubleshooting.mdx @@ -3,6 +3,8 @@ title: Troubleshooting description: The failures whose cause is not obvious from the error message --- +import { Tab, Tabs } from 'fumadocs-ui/components/tabs' + Errors print one line to stderr, prefixed `Error:`, and exit `1` — except `sim whoami`, which exits `2` when it could not reach the API to check at all. Most say what to do next; the cases below are the ones that do not. @@ -85,6 +87,55 @@ editing the file by hand: sim --output table configure --set-output json ``` +## A command is missing that the documentation describes + +The docs track the current release, so a command that exists here and not in +`sim --help` usually means the installed CLI is older than the feature. Compare +`sim --version` against the published version and upgrade: + +```bash +sim --version +``` + +Then upgrade with the package manager you installed it with — using a different +one installs a second copy instead of replacing the executable on your `PATH`: + + + + ```bash + npm install -g sim@latest + ``` + + + ```bash + pnpm add -g sim@latest + ``` + + + ```bash + bun add -g sim@latest + ``` + + + ```bash + yarn global add sim@latest + ``` + + + +The CLI can also tell you this through a cached daily check on eligible +invocations, and the command it prints already matches your installation. It +stays quiet when stderr is redirected, in CI, and under `npm exec` or `npx`. + +## An update notice appears in output I am parsing + +It should not: the notice is written to stderr, never stdout, so `--output json` +piped to `jq` is unaffected. If something merges the two streams, silence it: + +```bash +export SIM_NO_UPDATE_CHECK=1 +``` + ## Anything else An unexpected error prints a stack trace. That is a bug in the CLI — please diff --git a/apps/docs/content/docs/integrations/file.mdx b/apps/docs/content/docs/integrations/file.mdx index 23cbb7cfdb5..f62a9765458 100644 --- a/apps/docs/content/docs/integrations/file.mdx +++ b/apps/docs/content/docs/integrations/file.mdx @@ -15,20 +15,20 @@ The File block is a built-in Sim block for working with files stored in the work With the File block, you can: -- **Read and extract content**: Load workspace file objects and extract their text content -- **Search workspace content**: Match a regular expression, or an exact piece of text, against the indexed lines of active workspace files with bounded line-level results +- **Read and extract content**: Load workspace file objects and extract their text content from selected files or one or more folders +- **Search workspace content**: Match a regular expression, or an exact piece of text, across the workspace or selected folder scopes with bounded line-level results - **Fetch from URLs**: Retrieve and parse files from external URLs with custom headers - **Write and append**: Create new workspace files or append content to existing ones - **Compress and decompress**: Bundle files into a .zip archive or extract an archive into the workspace - **Manage sharing**: Enable or disable a public share link for a file, with public, password, email, or SSO access modes -In Sim, the File block allows your agents to search, read, and extract text from workspace files, fetch and parse files from URLs, write or append content to files, bundle files into or out of .zip archives, and control public sharing access for a file—all programmatically as steps in a workflow. This makes it possible to explore workspace content, move file content into and out of a workflow, package outputs for download or transfer, and expose files to external users through a managed share link. +In Sim, the File block allows your agents to search, read, and extract text from workspace files, fetch and parse files from URLs, write or append content to files, bundle files into or out of .zip archives, and control public sharing access for a file—all programmatically as steps in a workflow. Folder selection is optional and supports multiple folders. Pick them from the workspace, or switch the field to advanced mode and type comma-separated paths, including a value from an earlier block. When no folder is selected, search spans the workspace and file pickers are unscoped. Selected folders are expanded when the workflow runs, so newly added files are included automatically. This makes it possible to explore workspace content, move file content into and out of a workflow, package outputs for download or transfer, and expose files to external users through a managed share link. {/* MANUAL-CONTENT-END */} ## Usage Instructions -Read workspace file objects, search indexed text across all active workspace files, extract the text content of files, fetch and parse files from URLs with optional headers, write new workspace files, append content to existing files, compress files into a .zip archive, extract a .zip archive into the workspace, or manage the public share link for a file. +Read workspace file objects, search indexed text across the workspace or selected folder scopes, extract the text content of files, fetch and parse files from URLs with optional headers, write new workspace files, append content to existing files, compress files into a .zip archive, extract a .zip archive into the workspace, or manage the public share link for a file. @@ -36,7 +36,7 @@ Read workspace file objects, search indexed text across all active workspace fil ### File Read -Read workspace file objects from selected files or canonical workspace file IDs. +Read workspace file objects from selected files, canonical workspace file IDs, or one or more workspace folders. #### Input @@ -44,6 +44,8 @@ Read workspace file objects from selected files or canonical workspace file IDs. | --------- | ---- | -------- | ----------- | | `fileId` | string | No | Canonical workspace file ID, or an array of canonical workspace file IDs. | | `fileInput` | file | No | Selected workspace file object. | +| `folderPaths` | array | No | Folders whose files are included, as canonical percent-encoded paths, e.g. \["/Reports/Q3%20Results"\]. Nested folders are included by default, and the folders are read at run time, so a file added later is picked up. | +| `includeSubfolders` | boolean | No | Whether nested folders are read too. Defaults to true; set false to take only the folders’ direct files. | #### Output @@ -53,7 +55,7 @@ Read workspace file objects from selected files or canonical workspace file IDs. ### File Get Content -Extract the text content of one or more workspace files from selected file objects or canonical workspace file IDs. +Extract the text content of workspace files selected directly, identified by canonical file ID, or collected from one or more workspace folders. #### Input @@ -61,16 +63,21 @@ Extract the text content of one or more workspace files from selected file objec | --------- | ---- | -------- | ----------- | | `fileId` | string | No | Canonical workspace file ID, or an array of canonical workspace file IDs. | | `fileInput` | file | No | Selected workspace file object, or an array of file objects. | +| `folderPaths` | array | No | Folders whose files are included, as canonical percent-encoded paths, e.g. \["/Reports/Q3%20Results"\]. Nested folders are included by default, and the folders are read at run time, so a file added later is picked up. | +| `includeSubfolders` | boolean | No | Whether nested folders are read too. Defaults to true; set false to take only the folders’ direct files. | +| `offset` | number | No | First line to return, 1-based. Applied to each selected file separately, so a multi-file selection returns the same window of each. Absent starts at the first line. | +| `limit` | number | No | How many lines to return from the offset. Absent reads to the end. Use this to read part of a long file instead of all of it; the response reports the total line count alongside the window. | #### Output | Parameter | Type | Description | | --------- | ---- | ----------- | | `contents` | array | Array of file text contents, one entry per file in input order | +| `lineRanges` | array | Present when a line range was requested: one entry per file, in the same order, with offset, lineCount, and totalLines | ### File Search -Search the indexed text of active workspace files for lines matching a regular expression, and return each matching line once with its file ID and line number. Coverage is what the index currently holds, so check "complete" and "indexStatus" before concluding that something is absent. +Search the indexed text of active workspace files for lines matching a query, and return each matching line once with its file ID and line number. By default the query is a regular expression; in exact mode it is matched verbatim and metacharacters are literal. Coverage is what the index currently holds. A term that is not found is only authoritative when "complete" is true AND "indexStatus" reports no skipped or partial files; otherwise it is unknown rather than absent, so re-check before creating something on the assumption it is missing. Narrow the search with folderPaths to confine it to one or more folder trees, which also narrows "indexStatus" to those trees. #### Input @@ -79,6 +86,8 @@ Search the indexed text of active workspace files for lines matching a regular e | `query` | string | Yes | A regular expression matched against each line, 3-512 characters. Supports "." "*" "+" "?" "\{n,m\}" and their lazy forms, character classes such as "\[a-z\]" and "\[^0-9\]", the classes \d \w \s and \D \W \S, alternation "\|", groups "\(...\)" and "\(?:...\)", the anchors "^" and "$", and the word boundary \b. Lookahead, lookbehind, backreferences, named groups, inline flags such as "\(?i\)", \p\{...\} and POSIX "\[\[:alpha:\]\]" classes are not supported, and a pattern cannot span a line break. The pattern must contain at least 3 consecutive literal characters that every match will include — write "error \d+" rather than "\w+ \d+". Escape any metacharacter you mean literally. Matching is case-insensitive until the pattern contains an uppercase letter you are searching for; uppercase inside an escape or a character class, such as \D or \[A-Z\], does not make it case-sensitive. When the workflow builder sets Match to exact instead, the query is matched verbatim and no metacharacter needs escaping. | | `mode` | string | No | How the query is read, chosen by the workflow builder: "regex" \(default\) as a regular expression, or "exact" as verbatim text. | | `maxResults` | number | No | Hard result cap configured by the workflow builder \(1-200, default 50\). | +| `folderPaths` | array | No | Folders the search is confined to, as canonical percent-encoded paths, e.g. \["/memory/user-a"\]. Absent searches the whole workspace. Scoping also narrows the reported index coverage, so "complete" describes the folders searched. | +| `includeSubfolders` | boolean | No | Whether the scope descends into nested folders. Defaults to true; set false to search only the folders’ direct files. | #### Output @@ -125,6 +134,7 @@ Create a new workspace file, either from text content or from an existing file. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `fileName` | string | No | File name \(e.g., "data.csv"\). Required when writing text; optional when storing a file, which keeps its own name unless this overrides it. If the name already exists, a numeric suffix is added automatically unless overwrite is enabled. | +| `folderPath` | string | No | Folder to create the file in. Omit for the workspace root. Canonical folder path, percent-encoded, e.g. "/Reports/Q3%20Results". The workspace root is "/". | | `content` | string | No | The text content to write to the file. Provide exactly one of content or fileInput. | | `fileInput` | file | No | An existing file to store in the workspace, such as one produced by an earlier tool. Use this for anything that is not text — PDFs, images, audio, archives. Provide exactly one of content or fileInput. | | `contentType` | string | No | MIME type for new files \(e.g., "text/plain"\). Auto-detected from the file extension, or taken from the stored file, if omitted. | @@ -148,6 +158,9 @@ Append content to an existing workspace file. The file must already exist. Conte | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `fileName` | string | Yes | Name of an existing workspace file to append to. | +| `folderPath` | string | No | Single folder in which to resolve the file name. Canonical folder path, percent-encoded, e.g. "/Reports/Q3%20Results". The workspace root is "/". Use folderPaths for multiple folders; do not provide both fields. | +| `folderPaths` | array | No | Folders to search for the named file. The name must resolve to exactly one file across the selected scopes. Do not provide folderPath as well. | +| `includeSubfolders` | boolean | No | Whether the folder scope includes nested folders. Defaults to true; set false to target only files directly in the folder. | | `content` | string | Yes | The text content to append to the file. | #### Output @@ -159,6 +172,38 @@ Append content to an existing workspace file. The file must already exist. Conte | `size` | number | File size in bytes | | `url` | string | URL to access the file | +### Apply File Edit + +Apply one precise edit to an existing text file without rewriting it. Use search_replace for verbatim replacement, optionally with replaceAll. Use replace_between, insert_after, or delete_between for complete trimmed-line anchors that stay stable when line numbers move. Folder scope can disambiguate a name or constrain a file ID. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `fileName` | string | Yes | Name or ID of the workspace file to edit. | +| `folderPath` | string | No | Single folder in which to resolve the file name or validate the file ID. Canonical folder path, percent-encoded, e.g. "/memory/user-a/people". The workspace root is "/". Use folderPaths for multiple folders; do not provide both fields. | +| `folderPaths` | array | No | Folders to search for the named file or validate the file ID against. The name must resolve to exactly one file across the selected scopes. Do not provide folderPath as well. | +| `includeSubfolders` | boolean | No | Whether selected folders are searched recursively. Defaults to true; false matches only their direct contents. | +| `mode` | string | Yes | Edit type: search_replace, replace_between, insert_after, or delete_between. | +| `search` | string | No | For search_replace, the exact text to replace, including whitespace and line breaks. It must be unique unless replaceAll is true. | +| `content` | string | No | Replacement or inserted text. Pass an empty string to delete a search match or clear the text between anchors. Omit only for delete_between. | +| `replaceAll` | boolean | No | For search_replace, replace every non-overlapping match. Defaults to false, which refuses an ambiguous match. | +| `beforeAnchor` | string | No | For replace_between, the complete line before the content to replace. Leading and trailing whitespace is ignored. | +| `afterAnchor` | string | No | For replace_between, the complete line after the content to replace. The anchor lines remain in the file. | +| `anchor` | string | No | For insert_after, the complete line after which content is inserted. | +| `startAnchor` | string | No | For delete_between, the complete first line to delete. The start anchor is removed. | +| `endAnchor` | string | No | For delete_between, the complete ending boundary line. The end anchor remains in the file. | +| `occurrence` | number | No | For anchored edits, which matching anchor occurrence to use, starting at 1. Defaults to 1. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | File ID | +| `name` | string | File name | +| `size` | number | File size in bytes | +| `lineCount` | number | Lines in the file after the edit | + ### File Compress Compress one or more workspace files into a single .zip archive stored in the workspace, for bundling files to download, transfer, or store. Preserves the workspace folder structure of the selected files. @@ -169,6 +214,8 @@ Compress one or more workspace files into a single .zip archive stored in the wo | --------- | ---- | -------- | ----------- | | `fileId` | string | No | Canonical workspace file ID, or an array of canonical workspace file IDs. | | `fileInput` | file | No | Selected workspace file object, or an array of file objects. | +| `folderPaths` | array | No | Folders whose files are included, as canonical percent-encoded paths, e.g. \["/Reports/Q3%20Results"\]. Nested folders are included by default, and the folders are read at run time, so a file added later is picked up. | +| `includeSubfolders` | boolean | No | Whether nested folders are read too. Defaults to true; set false to take only the folders’ direct files. | | `archiveName` | string | No | Name for the .zip archive \(e.g., "documents.zip"\). Defaults to the source file name when compressing a single file, otherwise "archive.zip". | #### Output @@ -223,4 +270,114 @@ Enable or disable the public share link for a workspace file, and set its access | `hasPassword` | boolean | Whether the share is password-protected | | `allowedEmails` | array | Allowed emails/domains for email or SSO access | +### List Files and Folders + +List what is inside a workspace folder: its subfolders and its files together. Lists direct children by default; set Recursive to walk the whole subtree. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `path` | string | No | Folder to list. Omit to list from the workspace root. Canonical folder path, percent-encoded, e.g. "/Reports/Q3%20Results". The workspace root is "/". | +| `recursive` | boolean | No | List everything beneath the path rather than only its direct children. Each entry carries its depth below the listed folder. | +| `depth` | number | No | Deepest level to include when recursive, counted from the listed folder. 1 is direct children. | +| `search` | string | No | Case-insensitive substring match against an entry name. Filters the result, so a deep match is still reported even when its parent folders do not match. | +| `limit` | number | No | Most entries to return, 200 by default. A listing cut short comes back with truncated set. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `path` | string | The folder that was listed. | +| `entries` | array | What the folder holds. Each entry has kind "folder" or "file", a name, and its depth below the listed folder. A folder carries its own canonical path; a file carries its id, size, type, and the canonical path of the folder holding it. | +| `truncated` | boolean | True when the limit cut the listing short, so more entries exist. | + +### Create File Folder + +Create a workspace file folder at a path. Parent folders are created as needed. Fails if a folder already exists at the path. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `path` | string | Yes | Path of the folder to create. Canonical folder path, percent-encoded, e.g. "/Reports/Q3%20Results". The workspace root is "/". | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `folder` | object | The created folder, with its name, canonical path, parent path, and timestamps. | + +### Move File Folder + +Move or rename a workspace file folder by giving its full destination path. Everything inside the folder moves with it. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `path` | string | Yes | Folder to move. Canonical folder path, percent-encoded, e.g. "/Reports/Q3%20Results". The workspace root is "/". | +| `destinationPath` | string | Yes | Full path the folder should have afterwards. Renaming is a destination whose parent is unchanged. Canonical folder path, percent-encoded, e.g. "/Reports/Q3%20Results". The workspace root is "/". | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `folder` | object | The folder at its new path. | +| `previousPath` | string | The path the folder had before the move. | + +### Delete File Folder + +Delete a workspace file folder. It moves to Recently deleted and can be brought back with Restore File Folder. Deleting a folder that still has contents requires the recursive option. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `path` | string | Yes | Folder to delete. Canonical folder path, percent-encoded, e.g. "/Reports/Q3%20Results". The workspace root is "/". | +| `recursive` | boolean | No | Also delete the folder’s nested folders and files. Without it, deleting a non-empty folder fails. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `path` | string | The folder that was deleted. | +| `deleted` | boolean | Always true when the operation succeeded. | +| `deletedItems` | object | Counts of the folders and files deleted alongside it. | + +### Restore File Folder + +Restore a deleted workspace file folder and its contents from Recently deleted. Addressed by folder ID, because a deleted folder has no live path. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `folderId` | string | Yes | ID of the deleted folder to restore. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `folder` | object | The restored folder at its live path. | +| `restoredItems` | object | Counts of the folders and files restored alongside it. | + +### Move File + +Move an existing workspace file into a folder. Moves the file itself; use Move File Folder to relocate a whole folder. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `fileId` | string | Yes | Canonical workspace file ID of the file to move. | +| `folderPath` | string | No | Destination folder. Omit to move the file to the workspace root. Canonical folder path, percent-encoded, e.g. "/Reports/Q3%20Results". The workspace root is "/". | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `fileId` | string | The file that was moved. | +| `folderPath` | string | The folder the file now lives in. | + diff --git a/apps/docs/content/docs/integrations/knowledge.mdx b/apps/docs/content/docs/integrations/knowledge.mdx index f1be279b661..02c044589cb 100644 --- a/apps/docs/content/docs/integrations/knowledge.mdx +++ b/apps/docs/content/docs/integrations/knowledge.mdx @@ -33,7 +33,7 @@ Integrate Knowledge into the workflow. Perform full CRUD operations on documents ### Knowledge Search -Search for similar content in a knowledge base using vector similarity +Search for similar content in a knowledge base by relevance #### Input @@ -43,7 +43,7 @@ Search for similar content in a knowledge base using vector similarity | `query` | string | No | Search query text \(optional when using tag filters\) | | `topK` | number | No | Number of most similar results to return \(1-100\) | | `tagFilters` | array | No | Array of tag filters with tagName and tagValue properties | -| `searchMode` | string | No | Retrieval mode: 'vector' \(default\) uses semantic similarity only, 'hybrid' also runs a full-text leg and fuses both | +| `searchMode` | string | No | Retrieval mode: 'hybrid' fuses a full-text leg with semantic similarity, 'vector' uses semantic similarity only; omit for the workspace's default | | `rerankerEnabled` | boolean | No | Whether to apply Cohere reranking to vector search results | | `rerankerModel` | string | No | Cohere rerank model to use \(one of: rerank-v4.0-pro, rerank-v4.0-fast, rerank-v3.5\) | | `rerankerInputCount` | number | No | Number of vector results sent to the Cohere reranker \(1–100\). Defaults to topK × 4 capped at 100. | diff --git a/apps/docs/content/docs/integrations/manageengine_sdp.mdx b/apps/docs/content/docs/integrations/manageengine_sdp.mdx new file mode 100644 index 00000000000..e89d1cb955e --- /dev/null +++ b/apps/docs/content/docs/integrations/manageengine_sdp.mdx @@ -0,0 +1,1396 @@ +--- +title: ManageEngine ServiceDesk Plus +description: Manage ServiceDesk Plus Cloud requests, problems, changes, assets, and knowledge base solutions +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* MANUAL-CONTENT-START:intro */} +[ManageEngine ServiceDesk Plus Cloud](https://www.manageengine.com/products/service-desk/) is ManageEngine's ITSM suite — the service desk that IT teams run requests, problems, changes, assets, and a knowledge base on. Sim talks to its v3 REST API, which exposes the same operations available in the web client. + +**Why ServiceDesk Plus?** +- **The whole ITIL spine in one product:** incidents and service requests, problem records, change workflow with stages and approvals, a CMDB-backed asset inventory, and a requester-facing knowledge base — all cross-referenceable by ID. +- **Portal-defined everything:** statuses, priorities, categories, groups, and change stages are configured per portal. The API resolves them by name, so a workflow written against one portal's vocabulary reads naturally rather than hardcoding opaque IDs. +- **A real query language on every list:** `search_criteria` supports `is`, `is not`, `contains`, `starts with`, `between`, and numeric and date comparisons, so filtering happens server-side instead of by pulling the queue and sifting it in a workflow. +- **User-defined fields:** every module carries `udf_*` custom fields, which these operations read and write alongside the built-ins. + +**Using ServiceDesk Plus in Sim** + +This integration covers 31 operations across five modules — full create, read, list, update, and delete on requests, problems, changes, assets, and knowledge base solutions, plus notes on requests, problems, and changes. Lookup fields (status, priority, category, technician, group, topic, product) are addressed by name or email rather than by internal ID, so an agent can act on a ticket without first resolving five reference tables. + +**Key benefits of using ServiceDesk Plus in Sim:** +- **Triage and routing:** read an incoming request, classify it from its content, then set category, priority, and support group in one update. +- **SLA watch:** list open requests sorted on `due_by_time`, compare against now, and escalate or reassign what is breaching. +- **Ticket deflection:** search solutions for an answer, then post it as a requester-visible note instead of queueing the ticket for a human. +- **Knowledge capture:** turn a resolved request and its notes into a new solution filed under an existing topic. +- **Problem management:** cluster recurring requests by category and open a problem record that links them. +- **Change calendars and asset inventory:** page scheduled changes into a weekly summary, or mirror the asset list into a Sim table for querying and charting. + +**Before you start** + +Connect your Zoho account from the block's credential field. Self-hosted deployments additionally need Sim's Zoho OAuth client configured in the [Zoho API console](https://api-console.zoho.com/). The same client serves Zoho Desk and ServiceDesk Plus — Zoho scopes are chosen per authorization request, not per registered client, so no second app is needed. + +Three things shape what will work: + +- **Data center.** Connecting requires a Zoho account in the **US** data center. Sim's authorize and token-exchange legs are pinned to `accounts.zoho.com`, and a Zoho access token is only valid in the data center that issued it. The **Data Center** field lists every documented host (US, EU, IN, AU, JP, CA, SA, UK, CN, AE) because that mapping is stable, but only US is reachable with a credential connected through Sim today. +- **Portal.** Accounts with more than one portal must set the **Portal** field to the portal's URL name — found under *ESM Directory → Service Desk Instances*. Leaving it empty addresses the account's default portal. +- **Scopes.** The connection requests `SDPOnDemand.{requests,problems,changes,assets,solutions}` at the exact verbs these operations use, plus `aaaserver.profile.READ` to label the credential. Nothing broader is requested, so an operation whose module your Zoho user cannot access will fail at ServiceDesk Plus rather than being silently skipped. + +**Things worth knowing** + +- **Required fields differ per module.** Requests need a subject; problems a title; changes a title, stage, and status; assets a name and an existing product; solutions a title, content, and an existing topic. +- **Editing a change's status requires a comment.** ServiceDesk Plus rejects the update otherwise, so the **Status Comment** field sits alongside the status on Update Change. +- **Updates only send what you fill in.** Fields left empty are omitted from the request rather than sent as null, so changing a status never clears the technician or category. The two flags that cannot express this with a toggle — **Emergency Change** and **Visible to Requesters** — become *Leave unchanged / Yes / No* dropdowns on their update operations for the same reason. +- **A solution only becomes requester-visible once approved.** ServiceDesk Plus ignores `is_public` until the article's approval status is Approved. +- **Lists cap at 100 rows.** `Row Count` is clamped to the documented maximum; page with `Start Index` and watch `has_more_rows` in the returned `listInfo`. Set **Include Total Count** when you need the true size of a queue rather than the page size. +- **The standalone Tasks module is not covered.** Its endpoints are documented but the ServiceDesk Plus scope table publishes no `tasks` entry, so requesting one would put an unverified scope on the consent screen. +{/* MANUAL-CONTENT-END */} + + +## Usage Instructions + +Full read and write access to ManageEngine ServiceDesk Plus Cloud: create, search, update and delete requests, problems, changes, assets and knowledge base solutions, and add notes to requests, problems and changes. Supports multi-portal accounts. Connecting requires a Zoho account in the US data center. + + + +## Actions + +### ManageEngine SDP Create Request + +Create a request (ticket) in ManageEngine ServiceDesk Plus Cloud with a subject, description, requester and classification. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `dataCenter` | string | No | Zoho data center hosting the portal \(US, EU, IN, AU, JP, CA, SA, UK, CN, AE\). Credentials connected through Sim are issued by the US accounts server and are only valid against US. | +| `portal` | string | No | Portal URL name. Leave empty to use the account default portal | +| `subject` | string | Yes | Request subject \(maximum 250 characters\) | +| `description` | string | No | Request description. HTML is supported | +| `requesterEmail` | string | No | Email address of the requester. Defaults to the authenticated user | +| `priority` | string | No | Priority name, e.g. High | +| `status` | string | No | Status name, e.g. Open | +| `category` | string | No | Category name | +| `subcategory` | string | No | Subcategory name | +| `group` | string | No | Support group name | +| `technicianEmail` | string | No | Email address of the technician to assign | +| `urgency` | string | No | Urgency name | +| `impact` | string | No | Impact name | +| `requestType` | string | No | Request type name, e.g. Incident or Service Request | +| `udfFields` | json | No | Portal-defined custom fields, e.g. \{"udf_char1":"value"\} | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `request` | object | The created request | +| ↳ `id` | string | Request ID | +| ↳ `display_id` | string | Request number shown in the SDP UI | +| ↳ `subject` | string | Request subject | +| ↳ `description` | string | Request description \(HTML\) | +| ↳ `status` | json | Current status | +| ↳ `color` | string | Status colour | +| ↳ `in_progress` | boolean | Whether the status is in progress | +| ↳ `internal_name` | string | Internal status name | +| ↳ `stop_timer` | boolean | Whether the SLA timer stops | +| ↳ `priority` | json | Priority | +| ↳ `color` | string | Priority colour | +| ↳ `requester` | json | Requester | +| ↳ `technician` | json | Assigned technician | +| ↳ `group` | json | Support group | +| ↳ `site` | string | Site the group belongs to | +| ↳ `deleted` | boolean | Whether the group is deleted | +| ↳ `category` | json | Category | +| ↳ `subcategory` | json | Subcategory | +| ↳ `urgency` | json | Urgency | +| ↳ `impact` | json | Impact | +| ↳ `template` | json | Request template | +| ↳ `site` | json | Site | +| ↳ `created_time` | json | Creation time | +| ↳ `due_by_time` | json | SLA due time | +| ↳ `resolved_time` | json | Resolution time | +| ↳ `completed_time` | json | Completion time | +| ↳ `is_service_request` | boolean | Whether this is a service request rather than an incident | +| ↳ `has_notes` | boolean | Whether the request has notes | +| ↳ `udf_fields` | json | Portal-defined custom fields | + +### ManageEngine SDP Get Request + +Retrieve a single ManageEngine ServiceDesk Plus Cloud request by ID. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `dataCenter` | string | No | Zoho data center hosting the portal \(US, EU, IN, AU, JP, CA, SA, UK, CN, AE\). Credentials connected through Sim are issued by the US accounts server and are only valid against US. | +| `portal` | string | No | Portal URL name. Leave empty to use the account default portal | +| `requestId` | string | Yes | ID of the request to retrieve | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `request` | object | The request | +| ↳ `id` | string | Request ID | +| ↳ `display_id` | string | Request number shown in the SDP UI | +| ↳ `subject` | string | Request subject | +| ↳ `description` | string | Request description \(HTML\) | +| ↳ `status` | json | Current status | +| ↳ `color` | string | Status colour | +| ↳ `in_progress` | boolean | Whether the status is in progress | +| ↳ `internal_name` | string | Internal status name | +| ↳ `stop_timer` | boolean | Whether the SLA timer stops | +| ↳ `priority` | json | Priority | +| ↳ `color` | string | Priority colour | +| ↳ `requester` | json | Requester | +| ↳ `technician` | json | Assigned technician | +| ↳ `group` | json | Support group | +| ↳ `site` | string | Site the group belongs to | +| ↳ `deleted` | boolean | Whether the group is deleted | +| ↳ `category` | json | Category | +| ↳ `subcategory` | json | Subcategory | +| ↳ `urgency` | json | Urgency | +| ↳ `impact` | json | Impact | +| ↳ `template` | json | Request template | +| ↳ `site` | json | Site | +| ↳ `created_time` | json | Creation time | +| ↳ `due_by_time` | json | SLA due time | +| ↳ `resolved_time` | json | Resolution time | +| ↳ `completed_time` | json | Completion time | +| ↳ `is_service_request` | boolean | Whether this is a service request rather than an incident | +| ↳ `has_notes` | boolean | Whether the request has notes | +| ↳ `udf_fields` | json | Portal-defined custom fields | + +### ManageEngine SDP List Requests + +List ManageEngine ServiceDesk Plus Cloud requests, with optional search criteria, sorting and paging. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `dataCenter` | string | No | Zoho data center hosting the portal \(US, EU, IN, AU, JP, CA, SA, UK, CN, AE\). Credentials connected through Sim are issued by the US accounts server and are only valid against US. | +| `portal` | string | No | Portal URL name. Leave empty to use the account default portal | +| `rowCount` | number | No | Rows to return \(maximum 100\) | +| `startIndex` | number | No | One-based index of the first row to return | +| `sortField` | string | No | Field to sort on, e.g. created_time | +| `sortOrder` | string | No | Sort direction: asc or desc | +| `searchCriteria` | json | No | Search criteria object or array, e.g. \{"field":"status.name","condition":"is","value":"Open"\} | +| `fieldsRequired` | json | No | Array of field names to return, e.g. \["subject","status"\] | +| `getTotalCount` | boolean | No | Include the total matching row count in list_info | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `requests` | array | Matching requests | +| ↳ `id` | string | Request ID | +| ↳ `display_id` | string | Request number shown in the SDP UI | +| ↳ `subject` | string | Request subject | +| ↳ `description` | string | Request description \(HTML\) | +| ↳ `status` | json | Current status | +| ↳ `color` | string | Status colour | +| ↳ `in_progress` | boolean | Whether the status is in progress | +| ↳ `internal_name` | string | Internal status name | +| ↳ `stop_timer` | boolean | Whether the SLA timer stops | +| ↳ `priority` | json | Priority | +| ↳ `color` | string | Priority colour | +| ↳ `requester` | json | Requester | +| ↳ `technician` | json | Assigned technician | +| ↳ `group` | json | Support group | +| ↳ `site` | string | Site the group belongs to | +| ↳ `deleted` | boolean | Whether the group is deleted | +| ↳ `category` | json | Category | +| ↳ `subcategory` | json | Subcategory | +| ↳ `urgency` | json | Urgency | +| ↳ `impact` | json | Impact | +| ↳ `template` | json | Request template | +| ↳ `site` | json | Site | +| ↳ `created_time` | json | Creation time | +| ↳ `due_by_time` | json | SLA due time | +| ↳ `resolved_time` | json | Resolution time | +| ↳ `completed_time` | json | Completion time | +| ↳ `is_service_request` | boolean | Whether this is a service request rather than an incident | +| ↳ `has_notes` | boolean | Whether the request has notes | +| ↳ `udf_fields` | json | Portal-defined custom fields | +| `count` | number | Number of requests returned in this page | +| `listInfo` | object | Paging metadata echoed by ServiceDesk Plus | +| ↳ `row_count` | number | Rows returned | +| ↳ `start_index` | number | Index the page started at | +| ↳ `page` | number | Page number | +| ↳ `has_more_rows` | boolean | Whether more rows are available after this page | +| ↳ `sort_field` | string | Field the results were sorted on | +| ↳ `sort_order` | string | Sort direction | +| ↳ `total_count` | number | Total matching rows, present only when get_total_count was requested | + +### ManageEngine SDP Update Request + +Update a ManageEngine ServiceDesk Plus Cloud request: change its status, priority, assignment, classification or description. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `dataCenter` | string | No | Zoho data center hosting the portal \(US, EU, IN, AU, JP, CA, SA, UK, CN, AE\). Credentials connected through Sim are issued by the US accounts server and are only valid against US. | +| `portal` | string | No | Portal URL name. Leave empty to use the account default portal | +| `requestId` | string | Yes | ID of the request to update | +| `subject` | string | No | New subject \(maximum 250 characters\) | +| `description` | string | No | New description. HTML is supported | +| `priority` | string | No | Priority name to set, e.g. High | +| `status` | string | No | Status name to set, e.g. Resolved | +| `category` | string | No | Category name to set | +| `subcategory` | string | No | Subcategory name to set | +| `group` | string | No | Support group name to set | +| `technicianEmail` | string | No | Email address of the technician to assign | +| `urgency` | string | No | Urgency name to set | +| `impact` | string | No | Impact name to set | +| `udfFields` | json | No | Portal-defined custom fields to set, e.g. \{"udf_char1":"value"\} | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `request` | object | The updated request | +| ↳ `id` | string | Request ID | +| ↳ `display_id` | string | Request number shown in the SDP UI | +| ↳ `subject` | string | Request subject | +| ↳ `description` | string | Request description \(HTML\) | +| ↳ `status` | json | Current status | +| ↳ `color` | string | Status colour | +| ↳ `in_progress` | boolean | Whether the status is in progress | +| ↳ `internal_name` | string | Internal status name | +| ↳ `stop_timer` | boolean | Whether the SLA timer stops | +| ↳ `priority` | json | Priority | +| ↳ `color` | string | Priority colour | +| ↳ `requester` | json | Requester | +| ↳ `technician` | json | Assigned technician | +| ↳ `group` | json | Support group | +| ↳ `site` | string | Site the group belongs to | +| ↳ `deleted` | boolean | Whether the group is deleted | +| ↳ `category` | json | Category | +| ↳ `subcategory` | json | Subcategory | +| ↳ `urgency` | json | Urgency | +| ↳ `impact` | json | Impact | +| ↳ `template` | json | Request template | +| ↳ `site` | json | Site | +| ↳ `created_time` | json | Creation time | +| ↳ `due_by_time` | json | SLA due time | +| ↳ `resolved_time` | json | Resolution time | +| ↳ `completed_time` | json | Completion time | +| ↳ `is_service_request` | boolean | Whether this is a service request rather than an incident | +| ↳ `has_notes` | boolean | Whether the request has notes | +| ↳ `udf_fields` | json | Portal-defined custom fields | + +### ManageEngine SDP Delete Request + +Move a ManageEngine ServiceDesk Plus Cloud request to the trash. Deleting a request also removes its notes, tasks and worklogs. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `dataCenter` | string | No | Zoho data center hosting the portal \(US, EU, IN, AU, JP, CA, SA, UK, CN, AE\). Credentials connected through Sim are issued by the US accounts server and are only valid against US. | +| `portal` | string | No | Portal URL name. Leave empty to use the account default portal | +| `requestId` | string | Yes | ID of the request to delete | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `deleted` | boolean | Whether the request was deleted | + +### ManageEngine SDP Add Request Note + +Add a note to a ManageEngine ServiceDesk Plus Cloud request, optionally making it visible to the requester. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `dataCenter` | string | No | Zoho data center hosting the portal \(US, EU, IN, AU, JP, CA, SA, UK, CN, AE\). Credentials connected through Sim are issued by the US accounts server and are only valid against US. | +| `portal` | string | No | Portal URL name. Leave empty to use the account default portal | +| `requestId` | string | Yes | ID of the request to add the note to | +| `description` | string | Yes | Note body. HTML is supported | +| `showToRequester` | boolean | No | Whether the requester can see this note | +| `notifyTechnician` | boolean | No | Whether to notify the assigned technician | +| `markFirstResponse` | boolean | No | Whether this note counts as the first response for SLA purposes | +| `addToLinkedRequests` | boolean | No | Whether to copy the note to linked requests | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `note` | object | The created note | +| ↳ `id` | string | Note ID | +| ↳ `description` | string | Note body \(HTML\) | +| ↳ `show_to_requester` | boolean | Whether the note is visible to the requester | +| ↳ `notify_technician` | boolean | Whether the technician was notified | +| ↳ `mark_first_response` | boolean | Whether the note counts as the first response | +| ↳ `add_to_linked_requests` | boolean | Whether the note was copied to linked requests | +| ↳ `created_time` | json | Creation time | +| ↳ `created_by` | json | Note author | +| ↳ `request` | json | The request the note belongs to | +| ↳ `id` | string | Request ID | +| ↳ `display_id` | string | Request number | +| ↳ `subject` | string | Request subject | + +### ManageEngine SDP List Request Notes + +List the notes on a ManageEngine ServiceDesk Plus Cloud request. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `dataCenter` | string | No | Zoho data center hosting the portal \(US, EU, IN, AU, JP, CA, SA, UK, CN, AE\). Credentials connected through Sim are issued by the US accounts server and are only valid against US. | +| `portal` | string | No | Portal URL name. Leave empty to use the account default portal | +| `requestId` | string | Yes | ID of the request whose notes to list | +| `rowCount` | number | No | Rows to return \(maximum 100\) | +| `startIndex` | number | No | One-based index of the first row to return | +| `sortField` | string | No | Field to sort on, e.g. created_time | +| `sortOrder` | string | No | Sort direction: asc or desc | +| `searchCriteria` | json | No | Search criteria object or array, e.g. \{"field":"status.name","condition":"is","value":"Open"\} | +| `fieldsRequired` | json | No | Array of field names to return, e.g. \["subject","status"\] | +| `getTotalCount` | boolean | No | Include the total matching row count in list_info | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `notes` | array | Notes on the request | +| ↳ `id` | string | Note ID | +| ↳ `description` | string | Note body \(HTML\) | +| ↳ `show_to_requester` | boolean | Whether the note is visible to the requester | +| ↳ `notify_technician` | boolean | Whether the technician was notified | +| ↳ `mark_first_response` | boolean | Whether the note counts as the first response | +| ↳ `add_to_linked_requests` | boolean | Whether the note was copied to linked requests | +| ↳ `created_time` | json | Creation time | +| ↳ `created_by` | json | Note author | +| ↳ `request` | json | The request the note belongs to | +| ↳ `id` | string | Request ID | +| ↳ `display_id` | string | Request number | +| ↳ `subject` | string | Request subject | +| `count` | number | Number of notes returned in this page | +| `listInfo` | object | Paging metadata echoed by ServiceDesk Plus | +| ↳ `row_count` | number | Rows returned | +| ↳ `start_index` | number | Index the page started at | +| ↳ `page` | number | Page number | +| ↳ `has_more_rows` | boolean | Whether more rows are available after this page | +| ↳ `sort_field` | string | Field the results were sorted on | +| ↳ `sort_order` | string | Sort direction | +| ↳ `total_count` | number | Total matching rows, present only when get_total_count was requested | + +### ManageEngine SDP Create Problem + +Create a problem record in ManageEngine ServiceDesk Plus Cloud with a title, description and classification. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `dataCenter` | string | No | Zoho data center hosting the portal \(US, EU, IN, AU, JP, CA, SA, UK, CN, AE\). Credentials connected through Sim are issued by the US accounts server and are only valid against US. | +| `portal` | string | No | Portal URL name. Leave empty to use the account default portal | +| `title` | string | Yes | Problem title | +| `description` | string | No | Problem description. HTML is supported | +| `reportedByEmail` | string | No | Email address of the user reporting the problem | +| `technicianEmail` | string | No | Email address of the technician to assign | +| `priority` | string | No | Priority name, e.g. High | +| `status` | string | No | Status name, e.g. Open | +| `urgency` | string | No | Urgency name | +| `impact` | string | No | Impact name | +| `category` | string | No | Category name | +| `subcategory` | string | No | Subcategory name | +| `group` | string | No | Support group name | +| `site` | string | No | Site name | +| `udfFields` | json | No | Portal-defined custom fields, e.g. \{"udf_char1":"value"\} | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `problem` | object | The created problem | +| ↳ `id` | string | Problem ID | +| ↳ `display_id` | json | Problem number shown in the SDP UI \(display_value, value\) | +| ↳ `title` | string | Problem title | +| ↳ `description` | string | Problem description \(HTML\) | +| ↳ `status` | json | Current status | +| ↳ `priority` | json | Priority | +| ↳ `urgency` | json | Urgency | +| ↳ `impact` | json | Impact | +| ↳ `category` | json | Category | +| ↳ `subcategory` | json | Subcategory | +| ↳ `group` | json | Support group | +| ↳ `site` | json | Site | +| ↳ `reported_by` | json | User who reported the problem | +| ↳ `technician` | json | Assigned technician | +| ↳ `reported_time` | json | Time the problem was reported | +| ↳ `due_by_time` | json | Due time | +| ↳ `closed_time` | json | Time the problem was closed | +| ↳ `root_cause` | json | Root cause analysis | +| ↳ `workaround_details` | json | Documented workaround | +| ↳ `resolution_details` | json | Documented resolution | +| ↳ `known_error_details` | json | Known-error record details | +| ↳ `notes_present` | boolean | Whether the problem has notes | + +### ManageEngine SDP Get Problem + +Retrieve a single ManageEngine ServiceDesk Plus Cloud problem by ID. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `dataCenter` | string | No | Zoho data center hosting the portal \(US, EU, IN, AU, JP, CA, SA, UK, CN, AE\). Credentials connected through Sim are issued by the US accounts server and are only valid against US. | +| `portal` | string | No | Portal URL name. Leave empty to use the account default portal | +| `problemId` | string | Yes | ID of the problem to retrieve | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `problem` | object | The problem | +| ↳ `id` | string | Problem ID | +| ↳ `display_id` | json | Problem number shown in the SDP UI \(display_value, value\) | +| ↳ `title` | string | Problem title | +| ↳ `description` | string | Problem description \(HTML\) | +| ↳ `status` | json | Current status | +| ↳ `priority` | json | Priority | +| ↳ `urgency` | json | Urgency | +| ↳ `impact` | json | Impact | +| ↳ `category` | json | Category | +| ↳ `subcategory` | json | Subcategory | +| ↳ `group` | json | Support group | +| ↳ `site` | json | Site | +| ↳ `reported_by` | json | User who reported the problem | +| ↳ `technician` | json | Assigned technician | +| ↳ `reported_time` | json | Time the problem was reported | +| ↳ `due_by_time` | json | Due time | +| ↳ `closed_time` | json | Time the problem was closed | +| ↳ `root_cause` | json | Root cause analysis | +| ↳ `workaround_details` | json | Documented workaround | +| ↳ `resolution_details` | json | Documented resolution | +| ↳ `known_error_details` | json | Known-error record details | +| ↳ `notes_present` | boolean | Whether the problem has notes | + +### ManageEngine SDP List Problems + +List ManageEngine ServiceDesk Plus Cloud problems, with optional search criteria, sorting and paging. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `dataCenter` | string | No | Zoho data center hosting the portal \(US, EU, IN, AU, JP, CA, SA, UK, CN, AE\). Credentials connected through Sim are issued by the US accounts server and are only valid against US. | +| `portal` | string | No | Portal URL name. Leave empty to use the account default portal | +| `rowCount` | number | No | Rows to return \(maximum 100\) | +| `startIndex` | number | No | One-based index of the first row to return | +| `sortField` | string | No | Field to sort on, e.g. created_time | +| `sortOrder` | string | No | Sort direction: asc or desc | +| `searchCriteria` | json | No | Search criteria object or array, e.g. \{"field":"status.name","condition":"is","value":"Open"\} | +| `fieldsRequired` | json | No | Array of field names to return, e.g. \["subject","status"\] | +| `getTotalCount` | boolean | No | Include the total matching row count in list_info | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `problems` | array | Matching problems | +| ↳ `id` | string | Problem ID | +| ↳ `display_id` | json | Problem number shown in the SDP UI \(display_value, value\) | +| ↳ `title` | string | Problem title | +| ↳ `description` | string | Problem description \(HTML\) | +| ↳ `status` | json | Current status | +| ↳ `priority` | json | Priority | +| ↳ `urgency` | json | Urgency | +| ↳ `impact` | json | Impact | +| ↳ `category` | json | Category | +| ↳ `subcategory` | json | Subcategory | +| ↳ `group` | json | Support group | +| ↳ `site` | json | Site | +| ↳ `reported_by` | json | User who reported the problem | +| ↳ `technician` | json | Assigned technician | +| ↳ `reported_time` | json | Time the problem was reported | +| ↳ `due_by_time` | json | Due time | +| ↳ `closed_time` | json | Time the problem was closed | +| ↳ `root_cause` | json | Root cause analysis | +| ↳ `workaround_details` | json | Documented workaround | +| ↳ `resolution_details` | json | Documented resolution | +| ↳ `known_error_details` | json | Known-error record details | +| ↳ `notes_present` | boolean | Whether the problem has notes | +| `count` | number | Number of problems returned in this page | +| `listInfo` | object | Paging metadata echoed by ServiceDesk Plus | +| ↳ `row_count` | number | Rows returned | +| ↳ `start_index` | number | Index the page started at | +| ↳ `page` | number | Page number | +| ↳ `has_more_rows` | boolean | Whether more rows are available after this page | +| ↳ `sort_field` | string | Field the results were sorted on | +| ↳ `sort_order` | string | Sort direction | +| ↳ `total_count` | number | Total matching rows, present only when get_total_count was requested | + +### ManageEngine SDP Update Problem + +Update a ManageEngine ServiceDesk Plus Cloud problem: change its title, status, assignment or classification. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `dataCenter` | string | No | Zoho data center hosting the portal \(US, EU, IN, AU, JP, CA, SA, UK, CN, AE\). Credentials connected through Sim are issued by the US accounts server and are only valid against US. | +| `portal` | string | No | Portal URL name. Leave empty to use the account default portal | +| `problemId` | string | Yes | ID of the problem to update | +| `title` | string | No | New problem title | +| `description` | string | No | Problem description. HTML is supported | +| `reportedByEmail` | string | No | Email address of the user reporting the problem | +| `technicianEmail` | string | No | Email address of the technician to assign | +| `priority` | string | No | Priority name, e.g. High | +| `status` | string | No | Status name, e.g. Open | +| `urgency` | string | No | Urgency name | +| `impact` | string | No | Impact name | +| `category` | string | No | Category name | +| `subcategory` | string | No | Subcategory name | +| `group` | string | No | Support group name | +| `site` | string | No | Site name | +| `udfFields` | json | No | Portal-defined custom fields, e.g. \{"udf_char1":"value"\} | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `problem` | object | The updated problem | +| ↳ `id` | string | Problem ID | +| ↳ `display_id` | json | Problem number shown in the SDP UI \(display_value, value\) | +| ↳ `title` | string | Problem title | +| ↳ `description` | string | Problem description \(HTML\) | +| ↳ `status` | json | Current status | +| ↳ `priority` | json | Priority | +| ↳ `urgency` | json | Urgency | +| ↳ `impact` | json | Impact | +| ↳ `category` | json | Category | +| ↳ `subcategory` | json | Subcategory | +| ↳ `group` | json | Support group | +| ↳ `site` | json | Site | +| ↳ `reported_by` | json | User who reported the problem | +| ↳ `technician` | json | Assigned technician | +| ↳ `reported_time` | json | Time the problem was reported | +| ↳ `due_by_time` | json | Due time | +| ↳ `closed_time` | json | Time the problem was closed | +| ↳ `root_cause` | json | Root cause analysis | +| ↳ `workaround_details` | json | Documented workaround | +| ↳ `resolution_details` | json | Documented resolution | +| ↳ `known_error_details` | json | Known-error record details | +| ↳ `notes_present` | boolean | Whether the problem has notes | + +### ManageEngine SDP Delete Problem + +Delete a ManageEngine ServiceDesk Plus Cloud problem and its associated notes. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `dataCenter` | string | No | Zoho data center hosting the portal \(US, EU, IN, AU, JP, CA, SA, UK, CN, AE\). Credentials connected through Sim are issued by the US accounts server and are only valid against US. | +| `portal` | string | No | Portal URL name. Leave empty to use the account default portal | +| `problemId` | string | Yes | ID of the problem to delete | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `deleted` | boolean | Whether the problem was deleted | + +### ManageEngine SDP Add Problem Note + +Add a note to a ManageEngine ServiceDesk Plus Cloud problem. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `dataCenter` | string | No | Zoho data center hosting the portal \(US, EU, IN, AU, JP, CA, SA, UK, CN, AE\). Credentials connected through Sim are issued by the US accounts server and are only valid against US. | +| `portal` | string | No | Portal URL name. Leave empty to use the account default portal | +| `problemId` | string | Yes | ID of the problem to add the note to | +| `description` | string | Yes | Note body. HTML is supported | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `note` | object | The created note | +| ↳ `id` | string | Note ID | +| ↳ `description` | string | Note body \(HTML\) | +| ↳ `performed_by` | json | Note author | +| ↳ `performed_time` | json | Time the note was added | + +### ManageEngine SDP List Problem Notes + +List the notes on a ManageEngine ServiceDesk Plus Cloud problem. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `dataCenter` | string | No | Zoho data center hosting the portal \(US, EU, IN, AU, JP, CA, SA, UK, CN, AE\). Credentials connected through Sim are issued by the US accounts server and are only valid against US. | +| `portal` | string | No | Portal URL name. Leave empty to use the account default portal | +| `problemId` | string | Yes | ID of the problem whose notes to list | +| `rowCount` | number | No | Rows to return \(maximum 100\) | +| `startIndex` | number | No | One-based index of the first row to return | +| `sortField` | string | No | Field to sort on, e.g. created_time | +| `sortOrder` | string | No | Sort direction: asc or desc | +| `searchCriteria` | json | No | Search criteria object or array, e.g. \{"field":"status.name","condition":"is","value":"Open"\} | +| `fieldsRequired` | json | No | Array of field names to return, e.g. \["subject","status"\] | +| `getTotalCount` | boolean | No | Include the total matching row count in list_info | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `notes` | array | Notes on the problem | +| ↳ `id` | string | Note ID | +| ↳ `description` | string | Note body \(HTML\) | +| ↳ `performed_by` | json | Note author | +| ↳ `performed_time` | json | Time the note was added | +| `count` | number | Number of notes returned in this page | +| `listInfo` | object | Paging metadata echoed by ServiceDesk Plus | +| ↳ `row_count` | number | Rows returned | +| ↳ `start_index` | number | Index the page started at | +| ↳ `page` | number | Page number | +| ↳ `has_more_rows` | boolean | Whether more rows are available after this page | +| ↳ `sort_field` | string | Field the results were sorted on | +| ↳ `sort_order` | string | Sort direction | +| ↳ `total_count` | number | Total matching rows, present only when get_total_count was requested | + +### ManageEngine SDP Create Change + +Create a change record in ManageEngine ServiceDesk Plus Cloud with a title, stage, status and schedule. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `dataCenter` | string | No | Zoho data center hosting the portal \(US, EU, IN, AU, JP, CA, SA, UK, CN, AE\). Credentials connected through Sim are issued by the US accounts server and are only valid against US. | +| `portal` | string | No | Portal URL name. Leave empty to use the account default portal | +| `title` | string | Yes | Change title | +| `stage` | string | Yes | Change stage name, e.g. Submission | +| `status` | string | Yes | Change status name, e.g. Open | +| `description` | string | No | Change description. HTML is supported | +| `changeTypeName` | string | No | Change type name, e.g. Standard | +| `reasonForChange` | string | No | Reason-for-change name | +| `priority` | string | No | Priority name | +| `urgency` | string | No | Urgency name | +| `impact` | string | No | Impact name | +| `group` | string | No | Support group name | +| `changeRequesterEmail` | string | No | Email address of the change requester | +| `changeOwnerEmail` | string | No | Email address of the change owner | +| `changeManagerEmail` | string | No | Email address of the change manager | +| `scheduledStartTime` | string | No | Scheduled start, as an ISO 8601 timestamp or epoch milliseconds | +| `scheduledEndTime` | string | No | Scheduled end, as an ISO 8601 timestamp or epoch milliseconds | +| `emergency` | boolean | No | Whether this is an emergency change | +| `comment` | string | No | Reason for the status update. ServiceDesk Plus requires this when status changes | +| `udfFields` | json | No | Portal-defined custom fields, e.g. \{"udf_char1":"value"\} | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `change` | object | The created change | +| ↳ `id` | string | Change ID | +| ↳ `display_id` | json | Change number shown in the SDP UI \(display_value, value\) | +| ↳ `title` | string | Change title | +| ↳ `description` | string | Change description \(HTML\) | +| ↳ `status` | json | Current status | +| ↳ `internal_name` | string | Internal status name | +| ↳ `stage` | json | Change stage | +| ↳ `change_type` | json | Change type | +| ↳ `color` | string | Type colour | +| ↳ `pre_approved` | boolean | Whether the type is pre-approved | +| ↳ `priority` | json | Priority | +| ↳ `urgency` | json | Urgency | +| ↳ `impact` | json | Impact | +| ↳ `approval_status` | json | Approval status | +| ↳ `reason_for_change` | json | Reason for the change | +| ↳ `change_requester` | json | User who requested the change | +| ↳ `change_owner` | json | Change owner | +| ↳ `change_manager` | json | Change manager | +| ↳ `group` | json | Support group | +| ↳ `emergency` | boolean | Whether the change is an emergency | +| ↳ `created_time` | json | Creation time | +| ↳ `scheduled_start_time` | json | Scheduled start | +| ↳ `scheduled_end_time` | json | Scheduled end | +| ↳ `roll_out_plan` | json | Roll-out plan | +| ↳ `close_details` | json | Closure details | + +### ManageEngine SDP Get Change + +Retrieve a single ManageEngine ServiceDesk Plus Cloud change by ID. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `dataCenter` | string | No | Zoho data center hosting the portal \(US, EU, IN, AU, JP, CA, SA, UK, CN, AE\). Credentials connected through Sim are issued by the US accounts server and are only valid against US. | +| `portal` | string | No | Portal URL name. Leave empty to use the account default portal | +| `changeId` | string | Yes | ID of the change to retrieve | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `change` | object | The change | +| ↳ `id` | string | Change ID | +| ↳ `display_id` | json | Change number shown in the SDP UI \(display_value, value\) | +| ↳ `title` | string | Change title | +| ↳ `description` | string | Change description \(HTML\) | +| ↳ `status` | json | Current status | +| ↳ `internal_name` | string | Internal status name | +| ↳ `stage` | json | Change stage | +| ↳ `change_type` | json | Change type | +| ↳ `color` | string | Type colour | +| ↳ `pre_approved` | boolean | Whether the type is pre-approved | +| ↳ `priority` | json | Priority | +| ↳ `urgency` | json | Urgency | +| ↳ `impact` | json | Impact | +| ↳ `approval_status` | json | Approval status | +| ↳ `reason_for_change` | json | Reason for the change | +| ↳ `change_requester` | json | User who requested the change | +| ↳ `change_owner` | json | Change owner | +| ↳ `change_manager` | json | Change manager | +| ↳ `group` | json | Support group | +| ↳ `emergency` | boolean | Whether the change is an emergency | +| ↳ `created_time` | json | Creation time | +| ↳ `scheduled_start_time` | json | Scheduled start | +| ↳ `scheduled_end_time` | json | Scheduled end | +| ↳ `roll_out_plan` | json | Roll-out plan | +| ↳ `close_details` | json | Closure details | + +### ManageEngine SDP List Changes + +List ManageEngine ServiceDesk Plus Cloud changes, with optional search criteria, sorting and paging. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `dataCenter` | string | No | Zoho data center hosting the portal \(US, EU, IN, AU, JP, CA, SA, UK, CN, AE\). Credentials connected through Sim are issued by the US accounts server and are only valid against US. | +| `portal` | string | No | Portal URL name. Leave empty to use the account default portal | +| `rowCount` | number | No | Rows to return \(maximum 100\) | +| `startIndex` | number | No | One-based index of the first row to return | +| `sortField` | string | No | Field to sort on, e.g. created_time | +| `sortOrder` | string | No | Sort direction: asc or desc | +| `searchCriteria` | json | No | Search criteria object or array, e.g. \{"field":"status.name","condition":"is","value":"Open"\} | +| `fieldsRequired` | json | No | Array of field names to return, e.g. \["subject","status"\] | +| `getTotalCount` | boolean | No | Include the total matching row count in list_info | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `changes` | array | Matching changes | +| ↳ `id` | string | Change ID | +| ↳ `display_id` | json | Change number shown in the SDP UI \(display_value, value\) | +| ↳ `title` | string | Change title | +| ↳ `description` | string | Change description \(HTML\) | +| ↳ `status` | json | Current status | +| ↳ `internal_name` | string | Internal status name | +| ↳ `stage` | json | Change stage | +| ↳ `change_type` | json | Change type | +| ↳ `color` | string | Type colour | +| ↳ `pre_approved` | boolean | Whether the type is pre-approved | +| ↳ `priority` | json | Priority | +| ↳ `urgency` | json | Urgency | +| ↳ `impact` | json | Impact | +| ↳ `approval_status` | json | Approval status | +| ↳ `reason_for_change` | json | Reason for the change | +| ↳ `change_requester` | json | User who requested the change | +| ↳ `change_owner` | json | Change owner | +| ↳ `change_manager` | json | Change manager | +| ↳ `group` | json | Support group | +| ↳ `emergency` | boolean | Whether the change is an emergency | +| ↳ `created_time` | json | Creation time | +| ↳ `scheduled_start_time` | json | Scheduled start | +| ↳ `scheduled_end_time` | json | Scheduled end | +| ↳ `roll_out_plan` | json | Roll-out plan | +| ↳ `close_details` | json | Closure details | +| `count` | number | Number of changes returned in this page | +| `listInfo` | object | Paging metadata echoed by ServiceDesk Plus | +| ↳ `row_count` | number | Rows returned | +| ↳ `start_index` | number | Index the page started at | +| ↳ `page` | number | Page number | +| ↳ `has_more_rows` | boolean | Whether more rows are available after this page | +| ↳ `sort_field` | string | Field the results were sorted on | +| ↳ `sort_order` | string | Sort direction | +| ↳ `total_count` | number | Total matching rows, present only when get_total_count was requested | + +### ManageEngine SDP Update Change + +Update a ManageEngine ServiceDesk Plus Cloud change: move its stage or status, reschedule it, or change its assignment. ServiceDesk Plus requires a comment whenever the status changes. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `dataCenter` | string | No | Zoho data center hosting the portal \(US, EU, IN, AU, JP, CA, SA, UK, CN, AE\). Credentials connected through Sim are issued by the US accounts server and are only valid against US. | +| `portal` | string | No | Portal URL name. Leave empty to use the account default portal | +| `changeId` | string | Yes | ID of the change to update | +| `title` | string | No | New change title | +| `stage` | string | No | Change stage name to set | +| `status` | string | No | Change status name to set. Requires a comment | +| `description` | string | No | Change description. HTML is supported | +| `changeTypeName` | string | No | Change type name, e.g. Standard | +| `reasonForChange` | string | No | Reason-for-change name | +| `priority` | string | No | Priority name | +| `urgency` | string | No | Urgency name | +| `impact` | string | No | Impact name | +| `group` | string | No | Support group name | +| `changeRequesterEmail` | string | No | Email address of the change requester | +| `changeOwnerEmail` | string | No | Email address of the change owner | +| `changeManagerEmail` | string | No | Email address of the change manager | +| `scheduledStartTime` | string | No | Scheduled start, as an ISO 8601 timestamp or epoch milliseconds | +| `scheduledEndTime` | string | No | Scheduled end, as an ISO 8601 timestamp or epoch milliseconds | +| `emergency` | boolean | No | Whether this is an emergency change | +| `comment` | string | No | Reason for the status update. ServiceDesk Plus requires this when status changes | +| `udfFields` | json | No | Portal-defined custom fields, e.g. \{"udf_char1":"value"\} | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `change` | object | The updated change | +| ↳ `id` | string | Change ID | +| ↳ `display_id` | json | Change number shown in the SDP UI \(display_value, value\) | +| ↳ `title` | string | Change title | +| ↳ `description` | string | Change description \(HTML\) | +| ↳ `status` | json | Current status | +| ↳ `internal_name` | string | Internal status name | +| ↳ `stage` | json | Change stage | +| ↳ `change_type` | json | Change type | +| ↳ `color` | string | Type colour | +| ↳ `pre_approved` | boolean | Whether the type is pre-approved | +| ↳ `priority` | json | Priority | +| ↳ `urgency` | json | Urgency | +| ↳ `impact` | json | Impact | +| ↳ `approval_status` | json | Approval status | +| ↳ `reason_for_change` | json | Reason for the change | +| ↳ `change_requester` | json | User who requested the change | +| ↳ `change_owner` | json | Change owner | +| ↳ `change_manager` | json | Change manager | +| ↳ `group` | json | Support group | +| ↳ `emergency` | boolean | Whether the change is an emergency | +| ↳ `created_time` | json | Creation time | +| ↳ `scheduled_start_time` | json | Scheduled start | +| ↳ `scheduled_end_time` | json | Scheduled end | +| ↳ `roll_out_plan` | json | Roll-out plan | +| ↳ `close_details` | json | Closure details | + +### ManageEngine SDP Delete Change + +Delete a ManageEngine ServiceDesk Plus Cloud change and its associated notes. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `dataCenter` | string | No | Zoho data center hosting the portal \(US, EU, IN, AU, JP, CA, SA, UK, CN, AE\). Credentials connected through Sim are issued by the US accounts server and are only valid against US. | +| `portal` | string | No | Portal URL name. Leave empty to use the account default portal | +| `changeId` | string | Yes | ID of the change to delete | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `deleted` | boolean | Whether the change was deleted | + +### ManageEngine SDP Add Change Note + +Add a note to a ManageEngine ServiceDesk Plus Cloud change. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `dataCenter` | string | No | Zoho data center hosting the portal \(US, EU, IN, AU, JP, CA, SA, UK, CN, AE\). Credentials connected through Sim are issued by the US accounts server and are only valid against US. | +| `portal` | string | No | Portal URL name. Leave empty to use the account default portal | +| `changeId` | string | Yes | ID of the change to add the note to | +| `description` | string | Yes | Note body. HTML is supported | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `note` | object | The created note | +| ↳ `id` | string | Note ID | +| ↳ `description` | string | Note body \(HTML\) | +| ↳ `performed_by` | json | Note author | +| ↳ `performed_time` | json | Time the note was added | + +### ManageEngine SDP List Change Notes + +List the notes on a ManageEngine ServiceDesk Plus Cloud change. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `dataCenter` | string | No | Zoho data center hosting the portal \(US, EU, IN, AU, JP, CA, SA, UK, CN, AE\). Credentials connected through Sim are issued by the US accounts server and are only valid against US. | +| `portal` | string | No | Portal URL name. Leave empty to use the account default portal | +| `changeId` | string | Yes | ID of the change whose notes to list | +| `rowCount` | number | No | Rows to return \(maximum 100\) | +| `startIndex` | number | No | One-based index of the first row to return | +| `sortField` | string | No | Field to sort on, e.g. created_time | +| `sortOrder` | string | No | Sort direction: asc or desc | +| `searchCriteria` | json | No | Search criteria object or array, e.g. \{"field":"status.name","condition":"is","value":"Open"\} | +| `fieldsRequired` | json | No | Array of field names to return, e.g. \["subject","status"\] | +| `getTotalCount` | boolean | No | Include the total matching row count in list_info | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `notes` | array | Notes on the change | +| ↳ `id` | string | Note ID | +| ↳ `description` | string | Note body \(HTML\) | +| ↳ `performed_by` | json | Note author | +| ↳ `performed_time` | json | Time the note was added | +| `count` | number | Number of notes returned in this page | +| `listInfo` | object | Paging metadata echoed by ServiceDesk Plus | +| ↳ `row_count` | number | Rows returned | +| ↳ `start_index` | number | Index the page started at | +| ↳ `page` | number | Page number | +| ↳ `has_more_rows` | boolean | Whether more rows are available after this page | +| ↳ `sort_field` | string | Field the results were sorted on | +| ↳ `sort_order` | string | Sort direction | +| ↳ `total_count` | number | Total matching rows, present only when get_total_count was requested | + +### ManageEngine SDP Create Asset + +Create an asset in the ManageEngine ServiceDesk Plus Cloud inventory. Requires a name and an existing product. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `dataCenter` | string | No | Zoho data center hosting the portal \(US, EU, IN, AU, JP, CA, SA, UK, CN, AE\). Credentials connected through Sim are issued by the US accounts server and are only valid against US. | +| `portal` | string | No | Portal URL name. Leave empty to use the account default portal | +| `name` | string | Yes | Asset name | +| `product` | string | Yes | Name of an existing product this asset is an instance of | +| `productType` | string | No | Product type name, e.g. Laptop | +| `assetTag` | string | No | Asset tag | +| `serialNumber` | string | No | Serial number | +| `barcode` | string | No | Barcode | +| `ipAddress` | string | No | IP address | +| `macAddress` | string | No | MAC address | +| `location` | string | No | Location | +| `state` | string | No | Asset state name, e.g. In Use | +| `vendor` | string | No | Vendor name | +| `department` | string | No | Department name | +| `site` | string | No | Site name | +| `userEmail` | string | No | Email address of the user the asset is assigned to | +| `stateHistoryComments` | string | No | Comment recorded against a state change | +| `udfFields` | json | No | Portal-defined custom fields, e.g. \{"udf_char1":"value"\} | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `asset` | object | The created asset | +| ↳ `id` | string | Asset ID | +| ↳ `name` | string | Asset name | +| ↳ `asset_tag` | string | Asset tag | +| ↳ `barcode` | string | Barcode | +| ↳ `serial_number` | string | Serial number | +| ↳ `ip_address` | string | IP address | +| ↳ `mac_address` | string | MAC address | +| ↳ `location` | string | Location | +| ↳ `state` | json | Asset state \(In Use, In Store, ...\) | +| ↳ `description` | string | State description | +| ↳ `product` | json | Product this asset is an instance of | +| ↳ `manufacturer` | string | Manufacturer | +| ↳ `part_no` | string | Part number | +| ↳ `product_type` | json | Product type | +| ↳ `vendor` | json | Vendor | +| ↳ `department` | json | Owning department | +| ↳ `site` | json | Site | +| ↳ `user` | json | User the asset is assigned to | +| ↳ `purchase_cost` | number | Purchase cost | +| ↳ `total_cost` | number | Total cost | +| ↳ `acquisition_date` | json | Acquisition date | +| ↳ `expiry_date` | json | Expiry date | +| ↳ `warranty_expiry` | json | Warranty expiry date | +| ↳ `is_loaned` | boolean | Whether the asset is on loan | +| ↳ `is_in_contract` | boolean | Whether the asset is covered by a contract | + +### ManageEngine SDP Get Asset + +Retrieve a single ManageEngine ServiceDesk Plus Cloud asset by ID. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `dataCenter` | string | No | Zoho data center hosting the portal \(US, EU, IN, AU, JP, CA, SA, UK, CN, AE\). Credentials connected through Sim are issued by the US accounts server and are only valid against US. | +| `portal` | string | No | Portal URL name. Leave empty to use the account default portal | +| `assetId` | string | Yes | ID of the asset to retrieve | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `asset` | object | The asset | +| ↳ `id` | string | Asset ID | +| ↳ `name` | string | Asset name | +| ↳ `asset_tag` | string | Asset tag | +| ↳ `barcode` | string | Barcode | +| ↳ `serial_number` | string | Serial number | +| ↳ `ip_address` | string | IP address | +| ↳ `mac_address` | string | MAC address | +| ↳ `location` | string | Location | +| ↳ `state` | json | Asset state \(In Use, In Store, ...\) | +| ↳ `description` | string | State description | +| ↳ `product` | json | Product this asset is an instance of | +| ↳ `manufacturer` | string | Manufacturer | +| ↳ `part_no` | string | Part number | +| ↳ `product_type` | json | Product type | +| ↳ `vendor` | json | Vendor | +| ↳ `department` | json | Owning department | +| ↳ `site` | json | Site | +| ↳ `user` | json | User the asset is assigned to | +| ↳ `purchase_cost` | number | Purchase cost | +| ↳ `total_cost` | number | Total cost | +| ↳ `acquisition_date` | json | Acquisition date | +| ↳ `expiry_date` | json | Expiry date | +| ↳ `warranty_expiry` | json | Warranty expiry date | +| ↳ `is_loaned` | boolean | Whether the asset is on loan | +| ↳ `is_in_contract` | boolean | Whether the asset is covered by a contract | + +### ManageEngine SDP List Assets + +List ManageEngine ServiceDesk Plus Cloud assets, with optional search criteria, sorting and paging. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `dataCenter` | string | No | Zoho data center hosting the portal \(US, EU, IN, AU, JP, CA, SA, UK, CN, AE\). Credentials connected through Sim are issued by the US accounts server and are only valid against US. | +| `portal` | string | No | Portal URL name. Leave empty to use the account default portal | +| `rowCount` | number | No | Rows to return \(maximum 100\) | +| `startIndex` | number | No | One-based index of the first row to return | +| `sortField` | string | No | Field to sort on, e.g. created_time | +| `sortOrder` | string | No | Sort direction: asc or desc | +| `searchCriteria` | json | No | Search criteria object or array, e.g. \{"field":"status.name","condition":"is","value":"Open"\} | +| `fieldsRequired` | json | No | Array of field names to return, e.g. \["subject","status"\] | +| `getTotalCount` | boolean | No | Include the total matching row count in list_info | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `assets` | array | Matching assets | +| ↳ `id` | string | Asset ID | +| ↳ `name` | string | Asset name | +| ↳ `asset_tag` | string | Asset tag | +| ↳ `barcode` | string | Barcode | +| ↳ `serial_number` | string | Serial number | +| ↳ `ip_address` | string | IP address | +| ↳ `mac_address` | string | MAC address | +| ↳ `location` | string | Location | +| ↳ `state` | json | Asset state \(In Use, In Store, ...\) | +| ↳ `description` | string | State description | +| ↳ `product` | json | Product this asset is an instance of | +| ↳ `manufacturer` | string | Manufacturer | +| ↳ `part_no` | string | Part number | +| ↳ `product_type` | json | Product type | +| ↳ `vendor` | json | Vendor | +| ↳ `department` | json | Owning department | +| ↳ `site` | json | Site | +| ↳ `user` | json | User the asset is assigned to | +| ↳ `purchase_cost` | number | Purchase cost | +| ↳ `total_cost` | number | Total cost | +| ↳ `acquisition_date` | json | Acquisition date | +| ↳ `expiry_date` | json | Expiry date | +| ↳ `warranty_expiry` | json | Warranty expiry date | +| ↳ `is_loaned` | boolean | Whether the asset is on loan | +| ↳ `is_in_contract` | boolean | Whether the asset is covered by a contract | +| `count` | number | Number of assets returned in this page | +| `listInfo` | object | Paging metadata echoed by ServiceDesk Plus | +| ↳ `row_count` | number | Rows returned | +| ↳ `start_index` | number | Index the page started at | +| ↳ `page` | number | Page number | +| ↳ `has_more_rows` | boolean | Whether more rows are available after this page | +| ↳ `sort_field` | string | Field the results were sorted on | +| ↳ `sort_order` | string | Sort direction | +| ↳ `total_count` | number | Total matching rows, present only when get_total_count was requested | + +### ManageEngine SDP Update Asset + +Update a ManageEngine ServiceDesk Plus Cloud asset: reassign it, change its state, or correct its inventory details. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `dataCenter` | string | No | Zoho data center hosting the portal \(US, EU, IN, AU, JP, CA, SA, UK, CN, AE\). Credentials connected through Sim are issued by the US accounts server and are only valid against US. | +| `portal` | string | No | Portal URL name. Leave empty to use the account default portal | +| `assetId` | string | Yes | ID of the asset to update | +| `name` | string | No | New asset name | +| `product` | string | No | Name of an existing product to reassign the asset to | +| `productType` | string | No | Product type name, e.g. Laptop | +| `assetTag` | string | No | Asset tag | +| `serialNumber` | string | No | Serial number | +| `barcode` | string | No | Barcode | +| `ipAddress` | string | No | IP address | +| `macAddress` | string | No | MAC address | +| `location` | string | No | Location | +| `state` | string | No | Asset state name, e.g. In Use | +| `vendor` | string | No | Vendor name | +| `department` | string | No | Department name | +| `site` | string | No | Site name | +| `userEmail` | string | No | Email address of the user the asset is assigned to | +| `stateHistoryComments` | string | No | Comment recorded against a state change | +| `udfFields` | json | No | Portal-defined custom fields, e.g. \{"udf_char1":"value"\} | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `asset` | object | The updated asset | +| ↳ `id` | string | Asset ID | +| ↳ `name` | string | Asset name | +| ↳ `asset_tag` | string | Asset tag | +| ↳ `barcode` | string | Barcode | +| ↳ `serial_number` | string | Serial number | +| ↳ `ip_address` | string | IP address | +| ↳ `mac_address` | string | MAC address | +| ↳ `location` | string | Location | +| ↳ `state` | json | Asset state \(In Use, In Store, ...\) | +| ↳ `description` | string | State description | +| ↳ `product` | json | Product this asset is an instance of | +| ↳ `manufacturer` | string | Manufacturer | +| ↳ `part_no` | string | Part number | +| ↳ `product_type` | json | Product type | +| ↳ `vendor` | json | Vendor | +| ↳ `department` | json | Owning department | +| ↳ `site` | json | Site | +| ↳ `user` | json | User the asset is assigned to | +| ↳ `purchase_cost` | number | Purchase cost | +| ↳ `total_cost` | number | Total cost | +| ↳ `acquisition_date` | json | Acquisition date | +| ↳ `expiry_date` | json | Expiry date | +| ↳ `warranty_expiry` | json | Warranty expiry date | +| ↳ `is_loaned` | boolean | Whether the asset is on loan | +| ↳ `is_in_contract` | boolean | Whether the asset is covered by a contract | + +### ManageEngine SDP Delete Asset + +Delete an asset from the ManageEngine ServiceDesk Plus Cloud inventory. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `dataCenter` | string | No | Zoho data center hosting the portal \(US, EU, IN, AU, JP, CA, SA, UK, CN, AE\). Credentials connected through Sim are issued by the US accounts server and are only valid against US. | +| `portal` | string | No | Portal URL name. Leave empty to use the account default portal | +| `assetId` | string | Yes | ID of the asset to delete | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `deleted` | boolean | Whether the asset was deleted | + +### ManageEngine SDP Create Solution + +Add an article to the ManageEngine ServiceDesk Plus Cloud knowledge base under an existing topic. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `dataCenter` | string | No | Zoho data center hosting the portal \(US, EU, IN, AU, JP, CA, SA, UK, CN, AE\). Credentials connected through Sim are issued by the US accounts server and are only valid against US. | +| `portal` | string | No | Portal URL name. Leave empty to use the account default portal | +| `title` | string | Yes | Solution title | +| `description` | string | Yes | Solution body. HTML is supported | +| `topic` | string | Yes | Name of an existing knowledge base topic to file the solution under | +| `keywords` | string | No | Search keywords for the solution | +| `isPublic` | boolean | No | Whether requesters can see the solution. Only takes effect once the solution is Approved | +| `udfFields` | json | No | Portal-defined custom fields, e.g. \{"udf_char1":"value"\} | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `solution` | object | The created solution | +| ↳ `id` | string | Solution ID | +| ↳ `display_id` | json | Solution number shown in the SDP UI \(display_value, value\) | +| ↳ `title` | string | Solution title | +| ↳ `description` | string | Solution body \(HTML\) | +| ↳ `topic` | json | Topic the solution is filed under | +| ↳ `parent_topic` | json | Parent topic | +| ↳ `approval_status` | json | Approval status | +| ↳ `keywords` | string | Search keywords | +| ↳ `is_public` | boolean | Whether requesters can see the solution | +| ↳ `likes` | number | Like count | +| ↳ `dislikes` | number | Dislike count | +| ↳ `no_of_hits` | number | View count | +| ↳ `created_time` | json | Creation time | +| ↳ `last_updated_time` | json | Last update time | +| ↳ `review_date` | json | Date the solution is due for review | +| ↳ `expiry_date` | json | Date the solution expires | +| ↳ `created_by` | json | Author | +| ↳ `last_updated_by` | json | User who last updated the solution | +| ↳ `udf_fields` | json | Portal-defined custom fields | + +### ManageEngine SDP Get Solution + +Retrieve a single ManageEngine ServiceDesk Plus Cloud knowledge base solution by ID. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `dataCenter` | string | No | Zoho data center hosting the portal \(US, EU, IN, AU, JP, CA, SA, UK, CN, AE\). Credentials connected through Sim are issued by the US accounts server and are only valid against US. | +| `portal` | string | No | Portal URL name. Leave empty to use the account default portal | +| `solutionId` | string | Yes | ID of the solution to retrieve | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `solution` | object | The solution | +| ↳ `id` | string | Solution ID | +| ↳ `display_id` | json | Solution number shown in the SDP UI \(display_value, value\) | +| ↳ `title` | string | Solution title | +| ↳ `description` | string | Solution body \(HTML\) | +| ↳ `topic` | json | Topic the solution is filed under | +| ↳ `parent_topic` | json | Parent topic | +| ↳ `approval_status` | json | Approval status | +| ↳ `keywords` | string | Search keywords | +| ↳ `is_public` | boolean | Whether requesters can see the solution | +| ↳ `likes` | number | Like count | +| ↳ `dislikes` | number | Dislike count | +| ↳ `no_of_hits` | number | View count | +| ↳ `created_time` | json | Creation time | +| ↳ `last_updated_time` | json | Last update time | +| ↳ `review_date` | json | Date the solution is due for review | +| ↳ `expiry_date` | json | Date the solution expires | +| ↳ `created_by` | json | Author | +| ↳ `last_updated_by` | json | User who last updated the solution | +| ↳ `udf_fields` | json | Portal-defined custom fields | + +### ManageEngine SDP List Solutions + +Search the ManageEngine ServiceDesk Plus Cloud knowledge base, with optional search criteria, sorting and paging. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `dataCenter` | string | No | Zoho data center hosting the portal \(US, EU, IN, AU, JP, CA, SA, UK, CN, AE\). Credentials connected through Sim are issued by the US accounts server and are only valid against US. | +| `portal` | string | No | Portal URL name. Leave empty to use the account default portal | +| `rowCount` | number | No | Rows to return \(maximum 100\) | +| `startIndex` | number | No | One-based index of the first row to return | +| `sortField` | string | No | Field to sort on, e.g. created_time | +| `sortOrder` | string | No | Sort direction: asc or desc | +| `searchCriteria` | json | No | Search criteria object or array, e.g. \{"field":"status.name","condition":"is","value":"Open"\} | +| `fieldsRequired` | json | No | Array of field names to return, e.g. \["subject","status"\] | +| `getTotalCount` | boolean | No | Include the total matching row count in list_info | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `solutions` | array | Matching solutions | +| ↳ `id` | string | Solution ID | +| ↳ `display_id` | json | Solution number shown in the SDP UI \(display_value, value\) | +| ↳ `title` | string | Solution title | +| ↳ `description` | string | Solution body \(HTML\) | +| ↳ `topic` | json | Topic the solution is filed under | +| ↳ `parent_topic` | json | Parent topic | +| ↳ `approval_status` | json | Approval status | +| ↳ `keywords` | string | Search keywords | +| ↳ `is_public` | boolean | Whether requesters can see the solution | +| ↳ `likes` | number | Like count | +| ↳ `dislikes` | number | Dislike count | +| ↳ `no_of_hits` | number | View count | +| ↳ `created_time` | json | Creation time | +| ↳ `last_updated_time` | json | Last update time | +| ↳ `review_date` | json | Date the solution is due for review | +| ↳ `expiry_date` | json | Date the solution expires | +| ↳ `created_by` | json | Author | +| ↳ `last_updated_by` | json | User who last updated the solution | +| ↳ `udf_fields` | json | Portal-defined custom fields | +| `count` | number | Number of solutions returned in this page | +| `listInfo` | object | Paging metadata echoed by ServiceDesk Plus | +| ↳ `row_count` | number | Rows returned | +| ↳ `start_index` | number | Index the page started at | +| ↳ `page` | number | Page number | +| ↳ `has_more_rows` | boolean | Whether more rows are available after this page | +| ↳ `sort_field` | string | Field the results were sorted on | +| ↳ `sort_order` | string | Sort direction | +| ↳ `total_count` | number | Total matching rows, present only when get_total_count was requested | + +### ManageEngine SDP Update Solution + +Update a ManageEngine ServiceDesk Plus Cloud knowledge base solution: revise its body, retitle it, or move it to another topic. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `dataCenter` | string | No | Zoho data center hosting the portal \(US, EU, IN, AU, JP, CA, SA, UK, CN, AE\). Credentials connected through Sim are issued by the US accounts server and are only valid against US. | +| `portal` | string | No | Portal URL name. Leave empty to use the account default portal | +| `solutionId` | string | Yes | ID of the solution to update | +| `title` | string | No | New solution title | +| `description` | string | No | New solution body. HTML is supported | +| `topic` | string | No | Name of an existing topic to move the solution to | +| `keywords` | string | No | Search keywords for the solution | +| `isPublic` | boolean | No | Whether requesters can see the solution. Only takes effect once the solution is Approved | +| `udfFields` | json | No | Portal-defined custom fields, e.g. \{"udf_char1":"value"\} | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `solution` | object | The updated solution | +| ↳ `id` | string | Solution ID | +| ↳ `display_id` | json | Solution number shown in the SDP UI \(display_value, value\) | +| ↳ `title` | string | Solution title | +| ↳ `description` | string | Solution body \(HTML\) | +| ↳ `topic` | json | Topic the solution is filed under | +| ↳ `parent_topic` | json | Parent topic | +| ↳ `approval_status` | json | Approval status | +| ↳ `keywords` | string | Search keywords | +| ↳ `is_public` | boolean | Whether requesters can see the solution | +| ↳ `likes` | number | Like count | +| ↳ `dislikes` | number | Dislike count | +| ↳ `no_of_hits` | number | View count | +| ↳ `created_time` | json | Creation time | +| ↳ `last_updated_time` | json | Last update time | +| ↳ `review_date` | json | Date the solution is due for review | +| ↳ `expiry_date` | json | Date the solution expires | +| ↳ `created_by` | json | Author | +| ↳ `last_updated_by` | json | User who last updated the solution | +| ↳ `udf_fields` | json | Portal-defined custom fields | + +### ManageEngine SDP Delete Solution + +Delete an article from the ManageEngine ServiceDesk Plus Cloud knowledge base, along with its comments and versions. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `dataCenter` | string | No | Zoho data center hosting the portal \(US, EU, IN, AU, JP, CA, SA, UK, CN, AE\). Credentials connected through Sim are issued by the US accounts server and are only valid against US. | +| `portal` | string | No | Portal URL name. Leave empty to use the account default portal | +| `solutionId` | string | Yes | ID of the solution to delete | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `deleted` | boolean | Whether the solution was deleted | + + diff --git a/apps/docs/content/docs/integrations/meta.json b/apps/docs/content/docs/integrations/meta.json index 382216cef33..69d1dac6bfc 100644 --- a/apps/docs/content/docs/integrations/meta.json +++ b/apps/docs/content/docs/integrations/meta.json @@ -157,6 +157,7 @@ "mailchimp", "mailgun", "managed_agent", + "manageengine_sdp", "mem0", "memory", "microsoft_ad", diff --git a/apps/docs/content/docs/platform/enterprise/access-control.mdx b/apps/docs/content/docs/platform/enterprise/access-control.mdx index 3ff1b78f6f2..929f0f56437 100644 --- a/apps/docs/content/docs/platform/enterprise/access-control.mdx +++ b/apps/docs/content/docs/platform/enterprise/access-control.mdx @@ -13,7 +13,7 @@ Access Control lets organization admins define permission groups that restrict w ## How it works -Access control is built around **permission groups**. Each group belongs to a specific organization and has a name, an optional description, a **workspace scope**, an optional **member** list, and a configuration that defines what its members can and cannot do. The organization's single **default group** is org-wide; every other group targets a **specific set of workspaces**. A non-default group with **no members** governs **all members** of its workspaces (including external members); adding members narrows it to only those people. Personal workspaces that do not belong to an organization have no permission groups. +Each group belongs to a specific organization and has a name, an optional description, a **workspace scope**, an optional **member** list, and a configuration that defines what its members can and cannot do. Personal or grandfathered workspaces that do not belong to an organization have no permission groups. Sim resolves the governing group for a user in a workspace deterministically: @@ -27,15 +27,15 @@ Assignment-time checks keep this unambiguous: a workspace has at most one all-me When a user runs a workflow or uses Chat, Sim reads the resolved group's configuration and applies it: - **In the executor:** If a workflow uses a disallowed block type or model provider, execution halts immediately with an error. This applies to both manual runs and scheduled or API-triggered deployments. -- **In Chat:** Disallowed blocks are filtered out of the block list so they cannot be added to a workflow. Disallowed tool types (MCP, custom tools, skills) are skipped if Sim attempts to use them. +- **In Chat:** Disallowed blocks are filtered out of the block list so they cannot be added to a workflow. Disallowed tool types (MCP, custom tools, skills) are **not** filtered during generation — Sim is told about the restriction but may still produce a step that uses one. Such a run fails at execution with an error naming the restriction. --- ## Setup -### 1. Open Access Control settings +### 1. Open Permission groups settings -Go to **Settings → Enterprise → Access Control** from any workspace in your organization. Permission groups are defined once at the organization level and apply to every workspace under it. Only organization owners and admins can manage them. +Go to **Settings → Organization → Permission groups** from any workspace in your organization. Permission groups are defined once at the organization level and apply to every workspace under it. Only organization owners and admins can manage them. @@ -45,105 +45,148 @@ Click **+ Create** and enter a name (required) and optional description. A group ### 3. Configure permissions -Click **Details** on a group, then open **Configure Permissions**. Non-default groups have a **Members** tab plus three restriction tabs; the default group has only the restriction tabs. +Click **Details** on a group to open its configuration. It has four tabs — **General**, **Model Providers**, **Blocks**, and **Platform**. Name, description, and the permission settings are buffered until you save; the workspace scope, the default-group switch, and member changes are applied immediately, so **Discard** does not undo them. -#### Members +Throughout the editor, a **checked** box means allowed. Clearing a checkbox is what applies a restriction. -A workspace-scoped group with **no members** applies to everyone in its workspaces (including external members). Add members here — searching your organization by name or email — to restrict the group to only those people; removing every member returns it to governing everyone. The default group ignores members and has no Members tab. +#### General + +Holds the group's **Name** and **Description**, the **Default group** switch, the **Workspaces** the group governs, and its **Members**. + +A workspace-scoped group with **no members** applies to everyone in its workspaces (including external members). Add members here — searching your organization by name or email — to restrict the group to only those people; removing every member returns it to governing everyone. The default group ignores members and governs every workspace in the organization. #### Model Providers Controls which AI model providers members of this group can use. - The list shows all providers available in Sim. + + +The list shows all providers available in Sim. - **All checked (default):** All providers are allowed. - **Subset checked:** Only the selected providers are allowed. Any workflow block or agent using a provider not on the list will fail at execution time. +Expand a provider row to reach its **model denylist**. Clearing individual models blocks exactly those models while leaving the rest of the provider available — useful when a provider is sanctioned but a specific model is not. + #### Blocks Controls which workflow blocks members can place and execute. - Blocks are split into two sections: **Core Blocks** (Agent, API, Condition, Function, etc.) and **Tools** (all integration blocks). + + +Blocks are split into two sections: **Core Blocks** (Agent, API, Condition, Function, etc.) and **Tools** (all integration blocks). - **All checked (default):** All blocks are allowed. - **Subset checked:** Only the selected blocks are allowed. Workflows that already contain a disallowed block will fail when run — they are not automatically modified. +Expand an integration block to reach its **tool denylist**. Clearing individual tools blocks those operations while leaving the rest of the integration usable — for example, allowing a member to read from a service but not to delete in it. + The `start_trigger` block (the entry point of every workflow) is always allowed and cannot be restricted. #### Platform -Controls visibility of platform features and modules. +Controls the modules, actions, and credentials available to group members. Every row refuses at the API, not only in the UI — clearing a box revokes the access; it does not merely hide a tab. - Each checkbox maps to a specific feature; checking it hides or disables that feature for group members. + -**Sidebar** +**Modules** -| Feature | Effect when checked | -|---------|-------------------| -| Knowledge Base | Hides the Knowledge Base section from the sidebar | -| Tables | Hides the Tables section from the sidebar | +| Feature | What clearing it withholds | +|---------|---------------------------| +| Chat | Revokes Chat. Members cannot ask Sim to build or edit anything. | +| Sim Mailer | Revokes the Sim Mailer inbox. Members cannot read or send mail. | -**Workflow Panel** +**Knowledge Base** -| Feature | Effect when checked | -|---------|-------------------| -| Copilot | Hides the Copilot panel inside the workflow editor | +| Feature | What clearing it withholds | +|---------|---------------------------| +| Knowledge Base | Revokes the Knowledge Base module. Members cannot open, search, or query any knowledge base. | +| Knowledge Base Creation | Prevents creating knowledge bases, leaving existing ones queryable. | +| Knowledge Base Uploads | Prevents uploading local documents, leaving sanctioned connectors as the only source. | -**Settings Tabs** +The **Knowledge Base** row also carries a **connector allowlist** — *Connectors knowledge bases may sync from*. Leave it untouched to allow every connector, or select a subset to limit which external sources a knowledge base may sync. -| Feature | Effect when checked | -|---------|-------------------| -| Integrations | Hides the Integrations tab in Settings | -| Secrets | Hides the Secrets tab in Settings | -| API Keys | Hides the Sim Keys tab in Settings | -| Files | Hides the Files tab in Settings | +**Tables** -**Tools** +| Feature | What clearing it withholds | +|---------|---------------------------| +| Tables | Revokes the Tables module. Members cannot read or write any table. | +| Table Creation | Prevents creating tables, leaving existing ones usable. | +| Table Export | Prevents downloading a whole table as CSV or JSON. | + +**Files** -| Feature | Effect when checked | -|---------|-------------------| -| MCP Tools | Disables the use of MCP tools in workflows and agents | -| Custom Tools | Disables the use of custom tools in workflows and agents | -| Skills | Disables the use of Sim Skills in workflows and agents | +| Feature | What clearing it withholds | +|---------|---------------------------| +| Files | Revokes the Files module. Members cannot list, upload, or download workspace files. | +| Public Sharing | Revokes public file sharing. Members cannot create a share link. | +| Bulk Download | Prevents downloading folders as an archive. | -**Deploy Tabs** +The **Public Sharing** row also carries an **auth-mode allowlist** — *Auth modes public file-share links may use* (Anyone with link, Password, Email, SSO). Select a subset to force share links onto stronger authentication. -| Feature | Effect when checked | -|---------|-------------------| -| API | Hides the API deployment tab | -| MCP | Hides the MCP deployment tab | -| Chat | Hides the Chat deployment tab | -| Template | Hides the Template deployment tab | +**Deployment** -**Features** +| Feature | What clearing it withholds | +|---------|---------------------------| +| Public API | Revokes public API access. Calls to a deployed workflow are refused. | +| API Deployment | Prevents deploying a workflow as an API endpoint. | +| MCP Server | Prevents exposing a workflow as an MCP server. | +| Chat Deployment | Prevents publishing a workflow as a chat. | +| Webhook Triggers | Prevents making a workflow reachable from an inbound webhook. | -| Feature | Effect when checked | -|---------|-------------------| -| Sim Mailer | Hides the Sim Mailer (Inbox) feature | -| Public API | Disables public API access for deployed workflows | +The **Chat Deployment** row also carries an **auth-mode allowlist** — *Auth modes chat deployments may use* (Public, Password, Email, SSO). + +**Tools** + +| Feature | What clearing it withholds | +|---------|---------------------------| +| MCP Tools | Blocks agents from calling MCP tools. | +| Custom Tools | Blocks agents from calling user-defined custom tools. | +| Skills | Blocks agents from loading skills. | +| Tool Auto-Approval | Prevents auto-approving tool calls, so every call must be confirmed. | **Logs** -| Feature | Effect when checked | -|---------|-------------------| -| Trace Spans | Hides trace span details in execution logs | +| Feature | What clearing it withholds | +|---------|---------------------------| +| Trace Spans | Withholds per-block trace spans from logs and from the API. | +| Log Export | Prevents downloading execution logs as a CSV. | +| Execution Cost | Withholds execution cost. Logs and member exports omit cost and token spend; organization-level data drains, configurable by org admins only, are not projected. | **Collaboration** -| Feature | Effect when checked | -|---------|-------------------| -| Invitations | Disables the ability to invite new members to the workspace | +| Feature | What clearing it withholds | +|---------|---------------------------| +| Invitations | Prevents inviting anyone to a workspace or to the organization. | +| Workspace Creation | Prevents creating new workspaces. A new one is not covered by any workspace-scoped group until you add it, though the organization's default group still governs it. | +| Member Directory | Withholds the member directory. Members cannot see the names or email addresses of other members. Organization owners and admins keep access — the roster routes exempt those roles. | -### 4. Choose who it applies to +**Credentials & Access** + +| Feature | What clearing it withholds | +|---------|---------------------------| +| Integrations | Revokes integration connections. Members cannot view, add, or remove an OAuth connection. | +| Secrets | Revokes secrets. Members cannot read, add, or change a workspace environment variable. | +| API Keys | Revokes workspace API keys. Members cannot list, create, or revoke one. | +| Personal API Keys | Prevents members from using a personal API key against this workspace. | +| Personal Credentials | Prevents connecting personal credentials, leaving only workspace-shared ones. | +| CLI Access | Prevents approving a CLI login, which mints a key for the public API. | + +##### Rows read from the organization default group + +Two rows — **Workspace Creation** and **Member Directory** — are read only from the organization's **default group**, because the act they govern names no workspace. On any other group the editor renders them inert, tags them **Organization**, and skips them in **Select All**. Set them on the default group. + +Five more rows — **Integrations**, **API Keys**, **Invitations**, **Personal API Keys**, and **CLI Access** — apply on the group in front of you for anything scoped to one of its workspaces. The account-level path of the same action falls back to the default group: minting a personal key, an organization-wide invitation, an account-level CLI login. To close one of these completely, set it on the default group as well. -A workspace-scoped group applies to **all members of its workspaces by default** — including external members. To restrict it to specific people instead, open **Configure Permissions → Members** and add members by searching your organization by name or email. Removing every member returns the group to governing everyone in its workspaces. +### 4. Choose who it applies to A user is governed by one group per workspace, so adding a user is rejected when it would conflict with another of their groups on a shared workspace (skipped rather than added in bulk). The default group ignores members entirely — it always governs everyone not covered by a workspace group. -Manage which workspaces a group governs from the **Workspaces** list in the group's **Details** view (Add and Remove). A non-default group is created targeting at least one workspace, but you can later remove all of them — a group with no workspaces simply governs nothing until you add one back. +A workspace also has at most one all-members group. Adding a workspace to a group, or removing a group's last member, is rejected when doing so would violate that — memberships and scopes are never silently moved. + +Manage which workspaces a group governs from the **Workspaces** list on the same **General** tab. A non-default group is created targeting at least one workspace, but you can later remove all of them — a group with no workspaces simply governs nothing until you add one back. External workspace members (people who have access to a workspace but belong to a different organization) can't be added as named members, but a workspace-scoped group with no members — and the organization default group — still governs them. @@ -166,47 +209,19 @@ This applies regardless of how the workflow is triggered — manually, via API, When a user opens Chat, their permission group is read before any block or tool suggestions are made: - Blocks not in the allowed list are filtered out of the block picker entirely — they do not appear as options. -- If Sim generates a workflow step that would use a disallowed tool (MCP, custom, or skills), that step is skipped and the reason is noted. - ---- - -## User membership rules - -- A user can belong to **multiple** permission groups, but **at most one** group governs them in any given workspace. -- For a given workspace, a non-default group the user is an **explicit member** of takes precedence over a non-default **all-members** group (one with no members) targeting that workspace, which takes precedence over the organization's **default group**. -- A workspace has **at most one all-members group**, and a user is an explicit member of **at most one** group per workspace. Adding a user, adding a workspace, or removing a group's last member is rejected when it would violate this — memberships and scopes are never silently moved. -- A workspace-scoped group with **no members** governs everyone in its workspaces (including external members); add members to narrow it to specific people. -- Users not covered by any workspace group fall under the organization's **default group** if one is set; otherwise no restrictions are applied to them. -- Only one group per organization can be the **default group**; it always applies to all workspaces, ignores members, and also governs external workspace members. -- Personal or grandfathered workspaces that do not belong to an organization have no permission groups. +- Generation is not gated for these three tool kinds, so Sim can still produce a step that uses one. The restriction is enforced when the run reaches it: execution fails with an error naming it. --- @@ -241,4 +252,4 @@ You can also set a server-level block allowlist using the `ALLOWED_INTEGRATIONS` ALLOWED_INTEGRATIONS=slack,gmail,agent,function,condition ``` -Once enabled, permission groups are managed through **Settings → Enterprise → Access Control** the same way as Sim Cloud. +Once enabled, permission groups are managed through **Settings → Organization → Permission groups** the same way as Sim Cloud. diff --git a/apps/docs/content/docs/platform/enterprise/audit-logs.mdx b/apps/docs/content/docs/platform/enterprise/audit-logs.mdx index 9be2eb67ad1..36ecdacfa4c 100644 --- a/apps/docs/content/docs/platform/enterprise/audit-logs.mdx +++ b/apps/docs/content/docs/platform/enterprise/audit-logs.mdx @@ -14,7 +14,7 @@ Audit logs give your organization a tamper-evident record of every significant a ### In the UI -Go to **Settings → Enterprise → Audit Logs** in your workspace. Logs are displayed in a table with the following columns: +Go to **Settings → Organization → Audit logs** in your workspace. Logs are displayed in a table with the following columns: @@ -149,4 +149,13 @@ AUDIT_LOGS_ENABLED=true NEXT_PUBLIC_AUDIT_LOGS_ENABLED=true ``` -Once enabled, audit logs are viewable in **Settings → Enterprise → Audit Logs** and accessible via the API. +Once enabled, audit logs are viewable in **Settings → Organization → Audit logs** and accessible via the API. + +`GET /api/v1/audit-logs` authenticates with an `x-api-key` whose owner is an admin or owner of an organization, so it is unreachable on a deployment where nobody belongs to one yet. On Sim Cloud it additionally requires an active Enterprise subscription; self-hosted, `AUDIT_LOGS_ENABLED` takes that role. The admin-key equivalent needs neither an organization nor a plan: + +```http +GET /api/v1/admin/audit-logs +x-admin-key: +``` + +It accepts the same filters plus `actorEmail`, drops `includeDeparted`, and paginates with `limit` (max 250) and `offset` instead of the organization endpoint's `limit` (max 100) and `cursor`. It returns entries across the whole deployment rather than one organization. `GET /api/v1/admin/audit-logs/` returns a single entry. Set `ADMIN_API_KEY` to use it — see the [self-hosted enterprise guide](/platform/enterprise/self-hosted). diff --git a/apps/docs/content/docs/platform/enterprise/custom-blocks.mdx b/apps/docs/content/docs/platform/enterprise/custom-blocks.mdx index a528416612d..45761df87d3 100644 --- a/apps/docs/content/docs/platform/enterprise/custom-blocks.mdx +++ b/apps/docs/content/docs/platform/enterprise/custom-blocks.mdx @@ -14,7 +14,7 @@ A custom block always runs the **latest deployed version** of its source workflo ## Common uses -Custom blocks turn a workflow one team owns into infrastructure the whole organization can safely reuse. Credentials and complexity stay with the block's author; everyone else gets a clean block that's always up to date. A few patterns: +Consumers see only the inputs and outputs by default, unless the author enables **Trace runs in consumer logs**. Identity splits at execution: the source workflow's owner supplies personal variables and delegated calls, and the source workspace supplies workspace variables and carries the billing. Common patterns: - **Internal API gateway.** Wrap an authenticated internal or partner endpoint — "Create Ticket", "Charge Account", "Provision User" — behind a block that takes only the business inputs. Teammates call it without the base URL, API key, or auth headers, and when the endpoint changes you update one workflow instead of every consumer's. - **Blessed knowledge lookup.** Package a vetted retrieval pipeline — chunking, filters, reranking — as "Search Company Docs" with a single query input, so teams reuse the approved retrieval instead of each rebuilding it. @@ -35,7 +35,7 @@ Custom blocks turn a workflow one team owns into infrastructure the whole organi ### 1. Open Custom blocks settings -Go to **Settings → Enterprise → Custom blocks** and click **Create block**. +Go to **Settings → Organization → Custom blocks** and click **Create block**. @@ -90,19 +90,19 @@ Click **Save changes**. The block is published immediately and becomes available ## Using a custom block -In the workflow editor, open the block toolbar. Published custom blocks appear under a **Custom blocks** section. Drag one onto the canvas like any other block, fill in its inputs (using the placeholders as a guide), and reference its outputs in downstream blocks. +In the workflow editor, open the block toolbar. Published custom blocks appear under a **Custom blocks** section. Drag one into your workflow like any other block, fill in its inputs (using the placeholders as a guide), and reference its outputs in downstream blocks. Consumers don't need any access to the source workflow. The block runs on its own, using only the inputs provided, and returns only the outputs you exposed. Its internal steps, models, and intermediate values stay hidden unless the block's publisher turned on **Trace runs in consumer logs**, in which case they appear under the block in the run's trace. - + --- ## Managing blocks -Open a block from **Settings → Enterprise → Custom blocks** to edit or delete it. +Open a block from **Settings → Organization → Custom blocks** to edit or delete it. - **Editing** changes only the block's presentation, interface, and trace policy — name, description, icon, input placeholders, exposed outputs, and whether runs are traced in consumer logs. The source workflow can't be re-pointed. - **Changing what the block does** is done by editing and **redeploying the source workflow**. The block picks up the new deployment automatically; there's nothing to republish. @@ -131,7 +131,7 @@ Open a block from **Settings → Enterprise → Custom blocks** to edit or delet }, { question: "Can consumers see the workflow behind a block?", - answer: "No. A custom block only exposes the inputs it needs and the outputs you chose to share. The source workflow, its steps, and its intermediate values are never visible, and consumers don't need any access to it." + answer: "A custom block only exposes the inputs it needs and the outputs you chose to share, and consumers don't need any access to the source workflow. Its steps and intermediate values stay hidden unless you enable Trace runs in consumer logs, which surfaces them in the consumer's run trace." }, { question: "Can I change which workflow a block points to?", diff --git a/apps/docs/content/docs/platform/enterprise/data-drains.mdx b/apps/docs/content/docs/platform/enterprise/data-drains.mdx index 78c9b6c2686..d8805a9fc40 100644 --- a/apps/docs/content/docs/platform/enterprise/data-drains.mdx +++ b/apps/docs/content/docs/platform/enterprise/data-drains.mdx @@ -4,6 +4,7 @@ description: Continuously export workflow logs, audit logs, and Chat data to you --- import { FAQ } from '@/components/ui/faq' +import { Image } from '@/components/ui/image' Data Drains let organization owners and admins on Enterprise plans continuously export Sim data to a destination they control — a customer-owned S3 bucket, Google Cloud Storage bucket, Azure Blob container, BigQuery table, Snowflake table, Datadog logs intake, or an HTTPS webhook. A drain runs on a schedule, picks up only new rows since its last successful run, and writes them to the destination. Viewing drain configuration and run history is restricted to owners and admins as well, since destinations expose internal bucket names, table identifiers, and webhook URLs. @@ -13,11 +14,11 @@ Drains are independent of [Data Retention](/platform/enterprise/data-retention) ## Setup -Go to **Settings → Enterprise → Data Drains** in your workspace, then click **New drain**. +Go to **Settings → Organization → Data drains** in your workspace, then click **New drain**. -![Data Drains settings page showing two configured drains — one exporting workflow logs to Amazon S3 daily, another exporting Copilot chats to an HTTPS webhook hourly](/static/enterprise/data-drains-list.png) + -![New data drain dialog with fields for name, source, cadence, destination, and S3 credentials](/static/enterprise/data-drains-new.png) + Each drain has four pieces: @@ -186,7 +187,7 @@ The **last 10 runs** for each drain are visible by expanding its row in the sett ## Pairing with Data Retention -Drains and [Data Retention](/platform/enterprise/data-retention) are independent modules. Sim does **not** gate retention on drain progress — if a drain is failing, retention will still purge data on its own schedule. This matches the model used by Datadog Archives and AWS CloudWatch + S3 Export: keep the two configurations orthogonal and let the customer pair them deliberately. +Drains and [Data Retention](/platform/enterprise/data-retention) are independent modules. Sim does **not** gate retention on drain progress — if a drain is failing, retention will still purge data on its own schedule. Keep the two configurations independent and pair them deliberately. To safely use both together, set the drain cadence shorter than the retention period for the same data category: @@ -207,10 +208,6 @@ After data lands in your bucket or webhook system, archive lifecycle (transition question: "Who can configure data drains?", answer: "Only organization owners and admins can view, create, edit, run, or delete drains. On Sim Cloud, the organization must be on an Enterprise plan." }, - { - question: "Will drained data be duplicated if a run fails?", - answer: "The drain cursor only advances on overall success, so a failure replays the same chunks on the next run. Every row has a stable `id` field and every webhook chunk has an `Idempotency-Key` header so receivers can dedupe." - }, { question: "Can I export multiple sources to the same destination?", answer: "Yes — create one drain per source, all pointing at the same bucket or endpoint. S3 destinations namespace by source automatically; webhook receivers can branch on the `X-Sim-Source` header." @@ -240,6 +237,29 @@ DATA_DRAINS_ENABLED=true NEXT_PUBLIC_DATA_DRAINS_ENABLED=true ``` -`NEXT_PUBLIC_DATA_DRAINS_ENABLED` shows the **Settings → Enterprise → Data Drains** page in the UI. `DATA_DRAINS_ENABLED` gates the server-side mutating endpoints and the cron dispatcher — when unset on a self-hosted deployment, drain create/update/delete/run requests return `404` and the dispatcher is a no-op. Both should be set to `true` together. +`NEXT_PUBLIC_DATA_DRAINS_ENABLED` shows the **Settings → Organization → Data drains** page in the UI. `DATA_DRAINS_ENABLED` gates the server-side mutating endpoints and the cron dispatcher — when unset on a self-hosted deployment, drain create/update/delete/run requests return `404` and the dispatcher is a no-op. Both should be set to `true` together. + +### Scheduling the dispatcher + +The dispatcher is an HTTP endpoint, not a self-scheduling job — something has to call it: + +``` +GET /api/cron/run-data-drains +``` + +It authenticates with a bearer token equal to `CRON_SECRET` and returns `401` when that variable is unset, so a self-hosted deployment must set it: + +```bash +openssl rand -hex 32 +``` + +Set the same value as `CRON_SECRET` on both the app and whatever invokes the endpoint. Generating it in your shell does not configure either one. + +Both shipped deployments schedule this endpoint hourly for you — Helm through `cronjobs.jobs.runDataDrains`, Docker Compose through its `cron` service. A deployment that runs neither schedules it itself: + +```bash +curl -H "Authorization: Bearer $CRON_SECRET" \ + https://sim.example.com/api/cron/run-data-drains +``` -Data Drains otherwise rely on the standard Trigger.dev background job infrastructure used elsewhere in Sim — no additional setup is required. The cron dispatcher runs hourly and fans out due drains as background jobs. +Each due drain is then fanned out as a `run-data-drain` background job, so the deployment also needs `TRIGGER_DEV_ENABLED` and a configured Trigger.dev project. Without it the dispatcher still claims the work and enqueues it to the database, but nothing drains that queue for this job type, so the runs stay pending and never execute. See [background jobs](/platform/self-hosting/background-jobs). diff --git a/apps/docs/content/docs/platform/enterprise/data-retention.mdx b/apps/docs/content/docs/platform/enterprise/data-retention.mdx index 819ab84ee01..f7e4cdaef0c 100644 --- a/apps/docs/content/docs/platform/enterprise/data-retention.mdx +++ b/apps/docs/content/docs/platform/enterprise/data-retention.mdx @@ -18,7 +18,7 @@ Both are configured once at the **organization level** and apply to every worksp ## Setup -Go to **Settings → Enterprise → Data Retention** in your workspace. +Go to **Settings → Organization → Data retention** in your workspace. @@ -97,12 +97,12 @@ The **Workflow input** and **Block outputs** stages alter what the workflow comp For each stage, choose the **entity types** to redact from the searchable grid. They are grouped as: -- **Common** — person name, email, phone, credit card, IP address, URL, IBAN, crypto wallet, medical license, VIN +- **Common** — person name, email, phone, credit card, IP address, location, date or time, URL, IBAN, crypto wallet, nationality/religious/political group, medical license, VIN - **United States** — SSN, passport, driver's license, bank account, ITIN - **United Kingdom** — NHS number, National Insurance number -- **Other regions** — Singapore, Australian, and Indian identifiers +- **Other regions** — Spanish (NIF, NIE), Italian (fiscal code, driver's licence, VAT code, passport, identity card), Polish (PESEL), Singaporean (NRIC/FIN, UEN), Australian (ABN, ACN, TFN, Medicare), Indian (PAN, Aadhaar, vehicle registration, voter ID, passport), and Finnish (personal identity code) identifiers -The **Block outputs** stage is restricted to regex- and checksum-based recognizers, so it can run in-flight over large payloads without a performance penalty. Types that need name-model detection — person name, location, date or time — are not offered for that stage. +The **Block outputs** stage is restricted to regex- and checksum-based recognizers, so it can run in-flight over large payloads without a performance penalty. Types that need name-model detection — person name, location, date or time, and nationality/religious/political group — are not offered for that stage. Detection is language-aware: pick the **language** whose recognizers should apply. English, Spanish, Italian, Polish, and Finnish are supported, and the grid filters to the identifiers available for the selected language. @@ -167,7 +167,7 @@ By default, retention settings are unconfigured — no data is automatically del }, { question: "What is the maximum retention period?", - answer: "5 years." + answer: "5 years, or Forever — the same option list applies to all three settings." } ]} /> @@ -182,7 +182,30 @@ NEXT_PUBLIC_DATA_RETENTION_ENABLED=true DATA_RETENTION_ENABLED=true ``` -Once enabled, retention settings are configurable through **Settings → Enterprise → Data Retention** the same way as Sim Cloud. +Once enabled, retention settings are configurable through **Settings → Organization → Data retention** the same way as Sim Cloud. + +### Scheduling the deletion pass + +`DATA_RETENTION_ENABLED` permits deletion; it does not perform it. Deletion runs when a scheduled request reaches one of three endpoints, each authenticated with a bearer token equal to `CRON_SECRET`: + +| Category | Endpoint | +|----------|----------| +| Execution and job logs | `GET /api/logs/cleanup` | +| Soft-deleted resources | `GET /api/cron/cleanup-soft-deletes` | +| Chats and Chat runs | `GET /api/cron/cleanup-tasks` | + + +Neither shipped deployment schedules these three endpoints — not the Helm chart, not Docker Compose's `cron` service. An operator who sets `DATA_RETENTION_ENABLED=true` alone still deletes nothing. Add them to `cronjobs.jobs`, or call them daily from an external scheduler. + + +```bash +# Use the value already configured as CRON_SECRET on the app — a token +# generated here and not installed there returns 401. +curl -H "Authorization: Bearer $CRON_SECRET" \ + https://sim.example.com/api/logs/cleanup +``` + +Each call fans the work out as background jobs. Trigger.dev is not required: when it is not configured the dispatcher runs the cleanup chunks inline instead of enqueuing them, so deletion completes on a default self-host. ### PII redaction @@ -190,7 +213,14 @@ PII redaction runs against a standalone [Presidio](https://microsoft.github.io/p ```bash # The Presidio service exposing /analyze and /anonymize -PII_URL=http://localhost:5001 +# Helm — setting pii.enabled wires this for you, and a manual value is then +# ignored. Set it by hand only for an external Presidio with pii.enabled: +# false. The bundled Service is -sim-pii, or -pii when the +# release name already contains "sim". +PII_URL=http://..svc.cluster.local:5001 +# Docker Compose — no shipped file defines a PII service. Add one to the +# same project, then use its service name. +# PII_URL=http://pii:5001 ``` -All PII stages are configurable under **Settings → Enterprise → Data Retention**. +All PII stages are configurable under **Settings → Organization → Data retention**. diff --git a/apps/docs/content/docs/platform/enterprise/forks.mdx b/apps/docs/content/docs/platform/enterprise/forks.mdx index 62c0c60574a..281633bc3fa 100644 --- a/apps/docs/content/docs/platform/enterprise/forks.mdx +++ b/apps/docs/content/docs/platform/enterprise/forks.mdx @@ -29,7 +29,7 @@ On Sim Cloud, your organization may also need the feature turned on for your acc ### 1. Open Forks -Go to **Settings → Enterprise → Workspace Forks** in the workspace you want to fork from (or manage). +Go to **Settings → Organization → Workspace forks** in the workspace you want to fork from (or manage). @@ -62,7 +62,7 @@ Click **Fork**. The child workspace is created immediately. Deployed workflows l ### 3. Open the parent edge (from the child) -Open the **child** workspace → **Settings → Enterprise → Workspace Forks**. On the **Parent** row, open the menu and choose **Edit mappings**. +Open the **child** workspace → **Settings → Organization → Workspace forks**. On the **Parent** row, open the menu and choose **Edit mappings**. Child rows (when you are on the parent) only offer **Open workspace** and **Disconnect** — mapping and sync are owned by the child configuring how it relates to its parent. @@ -81,7 +81,7 @@ A **mapping** means: “this resource in the source is the same logical thing as Mappings are saved on the fork relationship. You can click **Save** without syncing. Sync always uses the saved mappings. -After you map or copy a parent resource (credential, knowledge base, table, …), **dependent fields** — Gmail labels, Slack channels, knowledge base documents, and similar — often need a fresh pick in the target. Required dependents block **Sync** until they are filled. +After you map or copy a parent resource (credential, knowledge base, table, …), **dependent fields** — Gmail labels, Slack channels, knowledge base documents, and similar — often need a fresh pick in the target. Required dependents block **Sync** until they are filled. Workspace file-folder scopes are mapped by canonical path; an empty folder selection creates no mapping requirement, and **Include Subfolders** does not block by itself. ### 5. Map, reconfigure, then Sync @@ -136,7 +136,7 @@ The setting belongs to **this workspace's copy** only. Excluding a workflow here -Expand a row for names of workflows and resources that were created, updated, or archived, and any warnings (for example failed background copies or deploy failures). +Expand a row for names of workflows and resources that were created, updated, or archived, and any warnings (for example failed background copies or deploy failures). A push or pull that copies resources fills their content in the background, and that progress and outcome show in the same row. --- @@ -173,6 +173,7 @@ How each resource behaves at **fork** time vs **sync** time. Use this when you a | Undeployed workflows | Not copied | Not synced | | [Excluded workflows](#excluded-workflows) | Never | Never — not sent, not overwritten, not archived | | Files | Optional copy (default on) | Map or copy | +| File folders referenced by workflows | Mirrored with their ancestor folders, even when empty | Map by canonical path | | Tables | Optional copy (default on) | Map or copy | | Knowledge bases (+ documents) | Optional copy; uploaded documents come with the KB, [connector-synced ones do not](#connector-synced-documents-are-not-copied) | Map or copy; documents follow the KB | | Custom tools | Optional copy (default on) | Map or copy | @@ -205,10 +206,12 @@ Only **deployed** workflows move. Deploy is the commit; sync is the force push/p | | Behavior | |---|----------| -| **Fork** | Listed under **Copy resources** (default on). Copies land at the child’s **file root** (original file folders are not rebuilt). Deselect → file fields in workflows clear. | -| **Sync** | Map to a file that already exists on the target, or copy. Files used by the sync’s workflows default to selected. | +| **Fork** | Listed under **Copy resources** (default on). A copied file keeps its containing folder structure. Folder scopes referenced by copied workflows are mirrored with their ancestors even when they are empty; mirroring a scope does not implicitly copy every file inside it. Deselect a file → that file field clears. | +| **Sync** | Map to a file that already exists on the target, or copy. Files used by the sync’s workflows default to selected. Selected workspace file-folder scopes are mapped separately by canonical path. The same path is suggested automatically when it exists on both sides; otherwise create or choose the intended target folder before syncing. | -**Example:** A workflow attaches `brand-guide.pdf`. Fork with Files selected → the child has its own copy and the block still points at it. +**Example:** A workflow attaches `Brand/brand-guide.pdf` and searches an empty `/Incoming` folder. Fork with Files selected → the child gets its own file under `/Brand`, and `/Incoming` is also created so the search scope still points somewhere valid without copying unrelated files into it. + +Only concrete, active workspace folder selections participate in this mapping. Leaving **Folder** empty means “anywhere in the workspace” and never blocks sync. **Include Subfolders** only changes how a selected scope is expanded. Folder selectors backed by external providers are credential-scoped provider values, not workspace file-folder mappings. --- @@ -328,6 +331,8 @@ Servers that **publish workflows as MCP tools**. | **Fork** | **Values never leave the source.** Workflow text still contains `{{KEY}}` names. Create matching secrets (or the names you will map to) under the child’s **Secrets**. | | **Sync** | Map source key names to target key names. Values stay in each workspace. Unmapped required secrets block Sync. | +Notes are documentation: a `{{KEY}}` that appears only inside a Note block never needs mapping and never blocks Sync. + **Example:** Workflows use `{{OPENAI_API_KEY}}`. After fork, add that secret in the child (or map `OPENAI_API_KEY` to whatever name the child uses) before runs and syncs succeed. --- @@ -358,7 +363,7 @@ Schedules, webhooks, and triggers are not live in the child until you **deploy** ## Edge cases to keep in mind -- **Force overwrite** — Sync does not merge canvas changes. Anything only on the target that conflicts with the source’s deployed workflows can be lost. Use the confirm dialog’s archive list carefully. +- **Force overwrite** — Sync does not merge editor changes. Anything only on the target that conflicts with the source’s deployed workflows can be lost. Use the confirm dialog’s archive list carefully. - **Deselect on fork** — Cleared references are intentional. Prefer copying the resource, or plan to reconnect it in the child. - **Background copy** — Right after fork, large tables / knowledge bases / files may still be copying. Check **Activity** if something looks empty. - **OAuth MCP** — Expect a reconnect in the child (and after a copied server on sync). @@ -370,13 +375,8 @@ Schedules, webhooks, and triggers are not live in the child until you **deploy** --- @@ -389,4 +389,4 @@ Self-hosted deployments turn Forks on with an environment variable instead of th |----------|-------------| | `FORKING_ENABLED`, `NEXT_PUBLIC_FORKING_ENABLED` | Enables workspace forking when billing is not used as the entitlement gate | -Once enabled, use the same **Settings → Enterprise → Workspace Forks** UI as Sim Cloud. Only workspace admins can manage forks. +Once enabled, use the same **Settings → Organization → Workspace forks** UI as Sim Cloud. Only workspace admins can manage forks. diff --git a/apps/docs/content/docs/platform/enterprise/index.mdx b/apps/docs/content/docs/platform/enterprise/index.mdx index fa3f62af3b1..acc35df3e0f 100644 --- a/apps/docs/content/docs/platform/enterprise/index.mdx +++ b/apps/docs/content/docs/platform/enterprise/index.mdx @@ -3,31 +3,29 @@ title: Enterprise description: Enterprise features for business organizations --- -import { FAQ } from '@/components/ui/faq' - -Sim Enterprise adds fine-grained access control, SSO, audit logging, compliance features, and workspace forking on top of Team plans. +Sim Enterprise adds permission groups, SSO, audit logs, usage tracking, data retention and drains, white-labeling, and workspace forks on top of Team plans. --- -## Access Control +## Permission groups -Define permission groups on a workspace to control what features and integrations its members can use. Permission groups are scoped to a single workspace — a user can belong to different groups (or no group) in different workspaces. +Define permission groups to control what features, models, blocks, and integrations your members can use. A permission group belongs to an **organization**. The organization's single **default group** governs everyone org-wide; every other group targets a specific set of workspaces and, by default, governs all members of those workspaces — or only named members once you add them. A user is governed by exactly one group in any given workspace. -External workspace members can be assigned to permission groups just like internal organization members, but they remain outside the organization roster and do not consume seats. +External workspace members can be governed by permission groups just like internal organization members, but they remain outside the organization roster and do not consume seats. -### Features +### What a group controls -- **Allowed Model Providers** - Restrict which AI providers users can access (OpenAI, Anthropic, Google, etc.) -- **Allowed Blocks** - Control which workflow blocks are available -- **Platform Settings** - Hide Knowledge Base, disable MCP tools, disable custom tools, or disable invitations +- **Model providers** - Restrict which AI providers members can use, and deny individual models within an allowed provider +- **Blocks** - Control which workflow blocks are available, and deny individual tools within an allowed integration +- **Platform** - Revoke modules (Chat, Knowledge Base, Tables, Files, Sim Mailer), deployment surfaces, tool types, log detail, collaboration actions, and credential access ### Setup -1. Navigate to **Settings** → **Access Control** in the workspace you want to manage +1. Navigate to **Settings → Organization → Permission groups** from any workspace in your organization 2. Create a permission group with your desired restrictions -3. Add workspace members to the permission group +3. Scope it to workspaces, and optionally add named members -Any workspace admin on an Enterprise-entitled workspace can manage permission groups. Users not assigned to any group have full access. Restrictions are enforced at both UI and execution time, based on the workflow's workspace. +Only organization owners and admins can manage permission groups. Users not governed by any group have full access. Restrictions are enforced at both UI and execution time, based on the organization that owns the workflow's workspace. See the [Access Control guide](/platform/enterprise/access-control) for full details. @@ -41,9 +39,9 @@ See the [SSO setup guide](/platform/enterprise/sso) for step-by-step instruction --- -## Whitelabeling +## White-labeling -Replace Sim's default branding — logos, product name, and favicons — with your own. See the [whitelabeling guide](/platform/enterprise/whitelabeling). +Replace Sim's default branding — logos, wordmark, product name, and theme colors — with your own. Instance-wide branding environment variables additionally cover the favicon and custom CSS. `NEXT_PUBLIC_BRAND_BACKGROUND_COLOR` is not a background — it only tells Sim whether your brand ground is dark, which it uses to pick contrasting text. See the [white-labeling guide](/platform/enterprise/whitelabeling). --- @@ -67,7 +65,7 @@ Configure how long execution logs, soft-deleted resources, and Chat data are kep ## Data Drains -Continuously export workflow logs, audit logs, and Chat data to a customer-owned S3 bucket or HTTPS webhook on a schedule. See the [data drains guide](/platform/enterprise/data-drains). +Continuously export workflow logs, audit logs, and Chat data to a destination you control — object storage, a data warehouse, Datadog, or an HTTPS webhook — on a schedule. See the [data drains guide](/platform/enterprise/data-drains). --- @@ -77,14 +75,6 @@ Clone a workspace into a linked child, then push or pull **deployed** workflow c --- - - ---- - ## Self-hosted setup Self-hosted deployments unlock enterprise features through environment configuration instead of billing. One switch turns on the whole set: diff --git a/apps/docs/content/docs/platform/enterprise/self-hosted.mdx b/apps/docs/content/docs/platform/enterprise/self-hosted.mdx index b059b7df99d..9e5b7dfe74a 100644 --- a/apps/docs/content/docs/platform/enterprise/self-hosted.mdx +++ b/apps/docs/content/docs/platform/enterprise/self-hosted.mdx @@ -12,7 +12,7 @@ On Sim Cloud, enterprise features are unlocked by an Enterprise subscription. Se There are two parts to getting this right, and skipping the second is the most common reason features appear to do nothing: 1. **Enable the features** with `ENTERPRISE_ENABLED`. -2. **Give them an organization to apply to.** Whitelabeling, PII redaction, permission groups, custom blocks, data drains, and audit scoping all read their settings from the organization that owns a workspace. A deployment where everyone works in personal workspaces has no organization for those settings to come from. +2. **Give them an organization to apply to.** White-labeling, PII redaction, permission groups, custom blocks, data drains, and audit scoping all read their settings from the organization that owns a workspace. A deployment where everyone works in personal workspaces has no organization for those settings to come from. ## Enable the feature set @@ -23,10 +23,9 @@ ENTERPRISE_ENABLED=true NEXT_PUBLIC_ENTERPRISE_ENABLED=true ``` -That turns on organizations, permission groups, SSO, whitelabeling, audit logs, -custom blocks, session policies, data retention, data drains, workspace forks, the Sandbox -entitlement, and the inbox. Sandboxes remain unavailable until their remote -provider and dedicated Function base are configured. +That turns on organizations, permission groups, SSO, white-labeling, audit logs, +usage tracking, custom blocks, session policies, data retention, data drains, workspace +forks, the Sandbox entitlement, and the inbox. ### Turning one feature off @@ -41,138 +40,62 @@ NEXT_PUBLIC_DATA_DRAINS_ENABLED=false The individual flags also work on their own if you would rather opt in one at a time and leave the master switch unset. +Three features do not need a flag at all: **custom branding**, **session policies**, and the **Sim Mailer inbox** are already on wherever billing is disabled, which is every self-hosted deployment. They preserve behavior that predates these flags. Set the corresponding variable to `false` to turn one off. + | Feature | Server variable | Client variable | |---------|-----------------|-----------------| | Everything below | `ENTERPRISE_ENABLED` | `NEXT_PUBLIC_ENTERPRISE_ENABLED` | | Organizations | `ORGANIZATIONS_ENABLED` | `NEXT_PUBLIC_ORGANIZATIONS_ENABLED` | | Permission groups | `ACCESS_CONTROL_ENABLED` | `NEXT_PUBLIC_ACCESS_CONTROL_ENABLED` | | SAML and OIDC sign-in | `SSO_ENABLED` | `NEXT_PUBLIC_SSO_ENABLED` | -| Custom branding | `WHITELABELING_ENABLED` | `NEXT_PUBLIC_WHITELABELING_ENABLED` | +| Custom branding — on by default | `WHITELABELING_ENABLED` | `NEXT_PUBLIC_WHITELABELING_ENABLED` | | Audit logs | `AUDIT_LOGS_ENABLED` | `NEXT_PUBLIC_AUDIT_LOGS_ENABLED` | +| Usage tracking | `USAGE_MONITORING_ENABLED` | `NEXT_PUBLIC_USAGE_MONITORING_ENABLED` | | Custom blocks | `CUSTOM_BLOCKS_ENABLED` | `NEXT_PUBLIC_CUSTOM_BLOCKS_ENABLED` | -| Session policies | `SESSION_POLICIES_ENABLED` | `NEXT_PUBLIC_SESSION_POLICIES_ENABLED` | +| Session policies — on by default | `SESSION_POLICIES_ENABLED` | `NEXT_PUBLIC_SESSION_POLICIES_ENABLED` | | Data retention deletion | `DATA_RETENTION_ENABLED` | `NEXT_PUBLIC_DATA_RETENTION_ENABLED` | | Data drains | `DATA_DRAINS_ENABLED` | `NEXT_PUBLIC_DATA_DRAINS_ENABLED` | -| Workspace forks | `FORKING_ENABLED` | — | -| Sim Mailer inbox | `INBOX_ENABLED` | `NEXT_PUBLIC_INBOX_ENABLED` | +| Workspace forks | `FORKING_ENABLED` | `NEXT_PUBLIC_FORKING_ENABLED` | +| Sim Mailer inbox — on by default | `INBOX_ENABLED` | `NEXT_PUBLIC_INBOX_ENABLED` | | Sandboxes | `SANDBOXES_ENABLED` | `NEXT_PUBLIC_SANDBOXES_ENABLED` | -Sandboxes also need a remote execution provider and a dedicated Function base -image. Build and configure that base before enabling the UI; custom workspace -sandboxes layer their packages on top of it. - -For E2B: - -```bash -E2B_API_KEY=... \ - bun run apps/sim/scripts/build-function-e2b-template.ts \ - --name sim-function - -SANDBOX_PROVIDER=e2b -E2B_ENABLED=true -E2B_API_KEY=... -E2B_FUNCTION_TEMPLATE_ID=: -E2B_FUNCTION_TEMPLATE_GENERATION= -SANDBOXES_ENABLED=true -NEXT_PUBLIC_SANDBOXES_ENABLED=true -``` - -The builder uses E2B's maintained `code-interpreter-v1` base, assigns a fresh -release generation, and prints both runtime values. `--generation` remains -available for release automation, and `--base-template` accepts an immutable -base override when a deployment deliberately owns one. +Sandboxes also need a remote execution provider and a dedicated Function base image before they can run anything. `SANDBOXES_ENABLED` grants the server-side entitlement; `NEXT_PUBLIC_SANDBOXES_ENABLED` projects provider readiness to the browser and exposes Shell plus custom Sandbox management. Set the public flag only after the selected provider has credentials and a valid immutable Function base configured. -For Daytona, use the immutable snapshot ID printed by the builder. The API key needs -`write:snapshots` to build and `write:sandboxes` to execute: +JavaScript without `import` or `require` does not use the remote provider and continues to run in the local isolated VM when all Sandbox flags are off. Python, Shell, JavaScript with external imports, and selected custom Sandboxes fail with an explicit configuration error until the remote Function base is ready. -```bash -DAYTONA_API_KEY=... \ - bun run apps/sim/scripts/build-function-daytona-snapshot.ts \ - --name sim-function-2026-08-03 \ - --parity-manifest /tmp/function-sandbox-manifest.json - -SANDBOX_PROVIDER=daytona -DAYTONA_API_KEY=... -DAYTONA_FUNCTION_SNAPSHOT_ID= -SANDBOXES_ENABLED=true -NEXT_PUBLIC_SANDBOXES_ENABLED=true -``` +See [Sandboxes](/platform/self-hosting/sandboxes) for the provider credentials, the Function base-image build, and the promotion procedure. -`SANDBOXES_ENABLED` grants the server-side self-hosted entitlement. -`NEXT_PUBLIC_SANDBOXES_ENABLED` projects provider readiness to the browser and -exposes Shell plus custom Sandbox management. Set the public flag only after the -selected provider has credentials and a valid immutable Function base configured. -The Function language value itself is never conditioned on these flags, so a -saved Python block cannot be silently serialized or executed as JavaScript. + + Data retention is the one feature that deletes data. Its flag controls the cleanup pass, not the settings screen — retention windows are always configurable. Nothing is ever deleted until you enable it, and even then only against windows you configured explicitly. Sim never applies the hosted plan defaults to a self-hosted deployment. + -JavaScript without `import` or `require` does not use this remote provider and -continues to run in the local isolated VM when all Sandbox flags are off. Python, -Shell, JavaScript with external imports, and selected custom Sandboxes fail with -an explicit configuration error until the remote Function base is ready. +## Schedule the background jobs -Mothership's `function_execute` and `run_code` tools use Mothership's separate -shell image, including for JavaScript without imports. If the deployment uses -Mothership code tools, also configure the image produced by the Mothership -release process for the selected provider: +Two enterprise features are started by a cron-driven HTTP endpoint rather than by the app on its own schedule. What happens next differs: retention's cleanup runs inline in the app process, while each due data drain is handed to a background job. All four endpoints authenticate with a bearer token equal to `CRON_SECRET`, and all four return `401` when it is unset: ```bash -# E2B -MOTHERSHIP_E2B_TEMPLATE_ID= - -# Daytona -DAYTONA_SHELL_SNAPSHOT_ID= +openssl rand -hex 32 ``` -These values are selected only for workflow Copilot and workspace Mothership -code-tool calls. They never replace or act as a fallback for -`E2B_FUNCTION_TEMPLATE_ID` or -`DAYTONA_FUNCTION_SNAPSHOT_ID`; Function blocks and custom workspace sandboxes -continue to use the dedicated Function base. - -Use E2B as the release baseline before building or promoting Daytona: +Persist that value as `CRON_SECRET` on the app **and** on whatever calls these endpoints. Generating it in a shell configures neither, and a mismatch returns `401`, so the work silently never runs. -```bash -# 1. Verify the exact E2B Function build and capture its accepted package/runtime surface. -E2B_ENABLED=true \ -E2B_API_KEY=... \ -E2B_FUNCTION_TEMPLATE_ID=: \ -E2B_FUNCTION_TEMPLATE_GENERATION= \ -SANDBOX_PARITY_MANIFEST_OUT=/tmp/function-sandbox-manifest.json \ - bun run apps/sim/scripts/verify-sandbox-parity.ts - -# 2. Pin Daytona's reconstructed packages to that accepted E2B manifest. -DAYTONA_API_KEY=... \ - bun run apps/sim/scripts/build-function-daytona-snapshot.ts \ - --name sim-function-2026-08-03 \ - --parity-manifest /tmp/function-sandbox-manifest.json - -# 3. Verify the immutable Daytona snapshot against the same baseline before promotion. -SANDBOX_PROVIDER=daytona \ -DAYTONA_API_KEY=... \ -DAYTONA_FUNCTION_SNAPSHOT_ID= \ -SANDBOX_PARITY_MANIFEST_BASELINE=/tmp/function-sandbox-manifest.json \ - bun run apps/sim/scripts/verify-sandbox-parity.ts -``` +| Feature | Endpoint | Suggested schedule | Scheduled for you | +|---------|----------|--------------------|-------------------| +| Data drains | `GET /api/cron/run-data-drains` | Hourly | Yes — Helm and Docker Compose both call it | +| Retention — logs | `GET /api/logs/cleanup` | Daily | **No** — schedule it yourself | +| Retention — soft deletes | `GET /api/cron/cleanup-soft-deletes` | Daily | **No** — schedule it yourself | +| Retention — Chat tasks | `GET /api/cron/cleanup-tasks` | Daily | **No** — schedule it yourself | - - `E2B_FUNCTION_TEMPLATE_ID` and `DAYTONA_FUNCTION_SNAPSHOT_ID` fail closed when - unset or mutable. The E2B value must be an exact ` - ## Requirements | Resource | Small | Standard | Production | @@ -29,7 +22,7 @@ Deploy Sim on your own infrastructure with Docker or Kubernetes. **Production**: Large teams (50+ users), high availability, heavy workflow execution -Resource requirements are driven by workflow execution (isolated-vm sandboxing), file processing (in-memory document parsing), and vector operations (pgvector). Memory is typically the constraining factor rather than CPU — both the Helm chart and the compose file request 4 Gi for the app and cap it at 8 Gi. +Resource requirements are driven by workflow execution (isolated-vm sandboxing), file processing (in-memory document parsing), and vector operations (pgvector). Memory is typically the constraining factor rather than CPU. The Helm chart requests 4 Gi for the app and caps it at 8 Gi; `docker-compose.prod.yml` sets only an 8 GB limit, with no reservation. ## Quick Start @@ -41,6 +34,7 @@ cat > .env << EOF BETTER_AUTH_SECRET=$(openssl rand -hex 32) ENCRYPTION_KEY=$(openssl rand -hex 32) INTERNAL_API_SECRET=$(openssl rand -hex 32) +API_ENCRYPTION_KEY=$(openssl rand -hex 32) CRON_SECRET=$(openssl rand -hex 32) EOF @@ -69,12 +63,12 @@ Open [http://localhost:3000](http://localhost:3000) ### Which one to pick -Both deploy the same feature set. Docker Compose is the fastest way to evaluate Sim and is fine for a single-node team install; Kubernetes is the path for high availability and managed secrets. +Docker Compose and Kubernetes run the same application; what differs is the operational envelope around it, which the table below sets out. Cloud Platforms covers provider-specific notes for either. Docker Compose is the fastest way to evaluate Sim and is fine for a single-node team install; Kubernetes is the path for high availability and managed secrets. | Capability | Docker Compose | Kubernetes (Helm) | |---|---|---| | App, realtime, migrations, Postgres, Redis | Yes | Yes | -| Scheduled workflows and polling triggers | Yes — `cron` service | Yes — 18 CronJobs | +| Scheduled workflows and polling triggers | Yes — `cron` service | Yes — 22 CronJobs | | Horizontal scaling / HA | No (single node) | Yes (`replicaCount`, HPA, PDB) | | Managed secrets (Vault, ESO, cloud KMS) | Manual `.env` | Yes | | Network policy, Pod Security Standards | Host-level only | Yes | @@ -119,10 +113,4 @@ Sim is self-contained for the core editor and execution engine. A few features r | **Agent blocks** | An API key for at least one model provider | Or a self-hosted OpenAI-compatible endpoint: Ollama, vLLM, LM Studio, or LiteLLM. | | **Chat module** | `COPILOT_API_KEY` from sim.ai | Set `NEXT_PUBLIC_CHAT_DISABLED=true` to hide the module instead. | | **Integrations** | Your own OAuth app per service | See [Integrations & OAuth](/platform/self-hosting/integrations-oauth). | -| **Remote Function / Pi execution** | Optional E2B or Daytona key | Without one, JavaScript Function code that has no `import` or `require` still runs in the in-process isolated VM. Python, Shell, JavaScript with external imports, custom Function Sandboxes, and Pi require a configured remote provider. See [Security](/platform/self-hosting/security). | - - +| **Remote Function / Pi execution** | Optional E2B or Daytona key, plus a Function base image | Plain JavaScript — no `import` or `require` — runs in the in-process isolated VM with nothing configured. Everything else needs both a provider key and an immutable Function base image: Python, Shell, imported JavaScript, custom Function Sandboxes, and any block that selects a sandbox or references a file path. Pi uses a separate image again, pinned with `E2B_PI_TEMPLATE_ID` or `DAYTONA_PI_SNAPSHOT_ID`. See [Sandboxes](/platform/self-hosting/sandboxes). | diff --git a/apps/docs/content/docs/platform/self-hosting/integrations-oauth.mdx b/apps/docs/content/docs/platform/self-hosting/integrations-oauth.mdx index d545871ec55..fe9bc70bb4a 100644 --- a/apps/docs/content/docs/platform/self-hosting/integrations-oauth.mdx +++ b/apps/docs/content/docs/platform/self-hosting/integrations-oauth.mdx @@ -110,7 +110,7 @@ One app registration in [Entra ID](https://entra.microsoft.com) covers all of th | Environment variables | Provider IDs | |---|---| -| `MICROSOFT_CLIENT_ID`
`MICROSOFT_CLIENT_SECRET` | `outlook`, `onedrive`, `sharepoint`, `microsoft-teams`, `microsoft-excel`, `microsoft-planner`, `microsoft-dataverse`, `microsoft-ad` | +| `MICROSOFT_CLIENT_ID`
`MICROSOFT_CLIENT_SECRET` | `outlook`, `onedrive`, `sharepoint`, `microsoft-teams`, `microsoft-excel`, `microsoft-word`, `microsoft-planner`, `microsoft-dataverse`, `microsoft-ad` | The same variables also power "Sign in with Microsoft". @@ -199,7 +199,7 @@ Webhook triggers receive callbacks from the provider and must be able to verify Your deployment must also be reachable from the provider's servers for webhook triggers to fire — a Sim instance on a private network can use polling triggers but not webhook triggers. Polling triggers additionally require the scheduler; see [Background Jobs](/platform/self-hosting/background-jobs). - Save all five values somewhere durable before moving on. `ENCRYPTION_KEY` in particular cannot be regenerated — losing it makes workspace environment variables and stored provider keys permanently unreadable. + Save all six values somewhere durable before moving on. `ENCRYPTION_KEY` in particular cannot be regenerated — losing it makes workspace environment variables and stored provider keys permanently unreadable. - `CRON_SECRET` is required, not optional: the chart enables background jobs by default and will not render without it. + `API_ENCRYPTION_KEY` is optional, and the failure mode is silent: leave it unset and Sim stores user-generated API keys **in plain text**, logging one warning and nothing else. Set it at install time — it must be a 64-character hex string, which is exactly what `openssl rand -hex 32` produces — and back it up like `ENCRYPTION_KEY`. + + `CRON_SECRET` is required whenever background jobs are enabled, which is the chart's default: the chart will not render without it, unless it comes from `app.secrets.existingSecret` or External Secrets — in which case the key must be present there or every cron pod fails to start. Set `cronjobs.enabled=false` instead if you deliberately want no scheduled jobs. @@ -55,49 +58,29 @@ helm install sim ./helm/sim \ ## Cloud-Specific Values -These are cloud-tuned **alternatives** to the generic install above — pick one path, don't run both. The commands reuse the `$BETTER_AUTH_SECRET`, `$ENCRYPTION_KEY`, `$INTERNAL_API_SECRET`, `$CRON_SECRET`, and `$POSTGRES_PASSWORD` variables generated in [Installation](#installation) above, so run that block's `openssl` lines first in the same shell. They use `helm upgrade --install`, so they work whether or not a release exists yet. Two caveats when converting an existing generic install rather than starting fresh: (1) **reuse the original secret values** — recover them with `helm get values sim -n simstudio` if your shell no longer has them; supplying a newly generated `ENCRYPTION_KEY` makes every previously encrypted value (workspace environment variables, stored provider keys, MCP OAuth credentials) undecryptable. (2) The cloud values rename the bundled PostgreSQL database to `simstudio`, but Postgres only applies that setting on first initialization — add `--set postgresql.auth.database=sim` to keep your existing database. If you'd rather start clean, `helm uninstall sim -n simstudio`, delete its PVCs, and run the cloud command fresh. +These are cloud-tuned **alternatives** to the generic install above — pick one path, don't run both. The commands reuse the `$BETTER_AUTH_SECRET`, `$ENCRYPTION_KEY`, `$INTERNAL_API_SECRET`, `$API_ENCRYPTION_KEY`, `$CRON_SECRET`, and `$POSTGRES_PASSWORD` variables generated in [Installation](#installation) above, so run that block's `openssl` lines first in the same shell. They use `helm upgrade --install`, so they work whether or not a release exists yet. Two caveats when converting an existing generic install rather than starting fresh: (1) **reuse the original secret values** — recover them with `helm get values sim -n simstudio` if your shell no longer has them; supplying a newly generated `ENCRYPTION_KEY` makes every previously encrypted value (workspace environment variables, stored provider keys, MCP OAuth credentials) undecryptable. (2) The cloud values rename the bundled PostgreSQL database to `simstudio`, but Postgres only applies that setting on first initialization — add `--set postgresql.auth.database=sim` to keep your existing database. If you'd rather start clean, `helm uninstall sim -n simstudio`, delete its PVCs, and run the cloud command fresh. - - ```bash helm upgrade --install sim ./helm/sim \ --values ./helm/sim/examples/values-aws.yaml \ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \ + --set app.env.API_ENCRYPTION_KEY="$API_ENCRYPTION_KEY" \ --set app.env.CRON_SECRET="$CRON_SECRET" \ --set postgresql.auth.password="$POSTGRES_PASSWORD" \ --set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \ + --set app.env.BETTER_AUTH_URL="https://sim.yourdomain.com" \ + --set app.env.NEXT_PUBLIC_SOCKET_URL="https://sim-ws.yourdomain.com" \ + --set realtime.env.ALLOWED_ORIGINS="https://sim.yourdomain.com" \ + --set ingress.app.host="sim.yourdomain.com" \ + --set ingress.realtime.host="sim-ws.yourdomain.com" \ --namespace simstudio --create-namespace ``` - - -```bash -helm upgrade --install sim ./helm/sim \ - --values ./helm/sim/examples/values-azure.yaml \ - --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \ - --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \ - --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \ - --set app.env.CRON_SECRET="$CRON_SECRET" \ - --set postgresql.auth.password="$POSTGRES_PASSWORD" \ - --set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \ - --namespace simstudio --create-namespace -``` - - -```bash -helm upgrade --install sim ./helm/sim \ - --values ./helm/sim/examples/values-gcp.yaml \ - --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \ - --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \ - --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \ - --set app.env.CRON_SECRET="$CRON_SECRET" \ - --set postgresql.auth.password="$POSTGRES_PASSWORD" \ - --set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \ - --namespace simstudio --create-namespace -``` - - + +Every one of those overrides is required. The cloud values files hardcode a placeholder domain in all six places, and overriding only `NEXT_PUBLIC_APP_URL` leaves sign-in pointed at the placeholder, realtime rejecting every socket upgrade, and the Ingress serving the wrong host. + +Swap the `--values` file for your cloud: `values-aws.yaml` (EKS), `values-azure.yaml` (AKS), or `values-gcp.yaml` (GKE). Everything else is identical. ## Key Configuration @@ -111,7 +94,7 @@ app: NEXT_PUBLIC_APP_URL: "https://sim.yourdomain.com" BETTER_AUTH_URL: "https://sim.yourdomain.com" OPENAI_API_KEY: "sk-..." - # Required once replicaCount > 1 + # Only needed when redis.enabled is false — the bundled Redis is injected otherwise REDIS_URL: "redis://:@redis.internal:6379" realtime: @@ -144,14 +127,14 @@ ingress: - Setting `replicaCount: 2` **without** `REDIS_URL` silently breaks live collaboration and status updates — cross-pod events are dropped with no error anywhere. See [Redis](/platform/self-hosting/redis) and [Scaling & HA](/platform/self-hosting/scaling). + The chart ships Redis (`redis.enabled: true`) and injects its `REDIS_URL` into both the app and realtime Deployments, so raising `replicaCount` works out of the box. The hazard is setting `redis.enabled: false` without supplying `app.env.REDIS_URL`: cross-pod events are then dropped with no error anywhere, silently breaking live collaboration and status updates. See [Redis](/platform/self-hosting/redis) and [Scaling & HA](/platform/self-hosting/scaling). See `helm/sim/values.yaml` for all options, and the chart's [README](https://github.com/simstudioai/sim/tree/main/helm/sim) for the production checklist. ## Background jobs -The chart deploys 18 CronJobs by default, driving scheduled workflows, polling triggers, connector syncs, data drains, and outbox processing. They require `CRON_SECRET`. +The chart deploys 22 CronJobs by default, driving scheduled workflows, polling triggers, connector syncs, data drains, and outbox processing. They require `CRON_SECRET`; set `cronjobs.enabled=false` to deploy none. ```bash kubectl get cronjobs -n simstudio @@ -206,9 +189,6 @@ helm uninstall sim --namespace simstudio ``` diff --git a/apps/docs/content/docs/platform/self-hosting/meta.json b/apps/docs/content/docs/platform/self-hosting/meta.json index f1373a9682f..5d6c37c8b7f 100644 --- a/apps/docs/content/docs/platform/self-hosting/meta.json +++ b/apps/docs/content/docs/platform/self-hosting/meta.json @@ -4,6 +4,7 @@ "index", "architecture", "---Install---", + "reference-architectures", "docker", "kubernetes", "platforms", @@ -17,6 +18,7 @@ "background-jobs", "networking", "security", + "sandboxes", "verify", "desktop", "---Operate---", diff --git a/apps/docs/content/docs/platform/self-hosting/networking.mdx b/apps/docs/content/docs/platform/self-hosting/networking.mdx index 47214f2582b..4502ea88979 100644 --- a/apps/docs/content/docs/platform/self-hosting/networking.mdx +++ b/apps/docs/content/docs/platform/self-hosting/networking.mdx @@ -5,7 +5,6 @@ description: Reverse proxies, TLS, websockets, timeouts, and request size limits import { Tab, Tabs } from 'fumadocs-ui/components/tabs' import { Callout } from 'fumadocs-ui/components/callout' -import { FAQ } from '@/components/ui/faq' Sim has three traffic patterns that trip up default proxy configurations: **long-lived websockets**, **server-sent event streams**, and **large uploads**. Most "it works locally but not in production" reports come from one of the three. @@ -56,6 +55,46 @@ Both hostnames need DNS records and TLS certificates. +## Second ingress and `copilot` routing + +The chart renders a second, independent Ingress from `ingressInternal` — its own class, hosts, TLS secret, and rules. Use it to publish the same Deployments on an internal-only hostname alongside the public one. + +```yaml +ingressInternal: + enabled: true + className: nginx-internal + app: + host: sim.internal.example.com + paths: + - path: / + pathType: Prefix + realtime: + host: sim.internal.example.com + paths: + - path: /socket.io + pathType: Prefix + tls: + enabled: true + secretName: sim-internal-tls +``` + +When `realtime.host` equals `app.host`, the realtime paths are folded into the app host's rule ahead of the catch-all — which is why `/socket.io` must come first. Give realtime a different host and it gets a rule of its own. + +Both `ingress` and `ingressInternal` accept an optional `copilot` block, commented out in `values.yaml` because the `copilot` service is off by default. It follows the same host-sharing rule: + +```yaml +# The route renders only when the service itself is enabled. +copilot: + enabled: true + +ingress: + copilot: + host: sim.yourdomain.com + paths: + - path: /copilot + pathType: Prefix +``` + ## Reverse proxy configuration @@ -187,16 +226,15 @@ spec: drainingTimeoutSec: 60 ``` -Then annotate the realtime Service so the load balancer picks it up: +Then annotate the realtime Service so the load balancer picks it up. The chart renders no annotations on any Service, and `realtime.service` accepts only `type`, `port`, and `targetPort` — an annotations key there is silently dropped. Annotate the Service directly: -```yaml -realtime: - service: - annotations: - cloud.google.com/backend-config: '{"default": "sim-realtime-backendconfig"}' +```bash +kubectl annotate service -n \ + -l app.kubernetes.io/instance=,app.kubernetes.io/component=realtime \ + cloud.google.com/backend-config='{"default": "sim-realtime-backendconfig"}' ``` -Confirm your chart version renders `realtime.service.annotations` onto the Service (`helm template ./helm/sim --values my-values.yaml | grep -A5 'kind: Service'`). If it does not, annotate the Service directly with `kubectl annotate`. +Re-apply it after any `helm upgrade` that recreates the Service, or manage the annotation with a kustomize patch so it survives. TLS on GKE typically uses a **ManagedCertificate**, which the chart references by annotation but **does not create** — create it yourself before the first deploy: @@ -204,7 +242,7 @@ TLS on GKE typically uses a **ManagedCertificate**, which the chart references b apiVersion: networking.gke.io/v1 kind: ManagedCertificate metadata: - name: sim-ssl-cert + name: simstudio-ssl-cert namespace: simstudio spec: domains: @@ -217,7 +255,7 @@ ingress: className: gce annotations: kubernetes.io/ingress.global-static-ip-name: "sim-ip" - networking.gke.io/managed-certificates: "sim-ssl-cert" + networking.gke.io/managed-certificates: "simstudio-ssl-cert" kubernetes.io/ingress.allow-http: "false" # TLS comes from the ManagedCertificate — leaving the chart's secret-based # TLS on makes the ingress reference a Secret that does not exist. @@ -264,10 +302,57 @@ A proxy body limit of 250 MB accommodates all three defaults. If you lower the a ## Outbound connectivity -The app makes outbound calls to model providers, integration APIs, your email provider, and object storage. There is no global forward-proxy setting. Sim does not read `HTTP_PROXY` / `HTTPS_PROXY`, so model-provider calls, integration calls, and email delivery cannot be routed through a forward proxy. (The HTTP Request block accepts a per-request `proxyUrl`, but that covers only that one block, not the platform's own outbound traffic.) Environments with a mandatory egress proxy need a transparent proxy or NAT-based egress instead. +The app makes outbound calls to model providers, integration APIs, your email provider, object storage, and your telemetry backend. Whether `HTTP_PROXY` / `HTTPS_PROXY` apply depends on which of those paths a call takes — there is no single answer, and no global setting that covers all of them. + +The server runs on Bun, and Bun's native `fetch` honors `$HTTP_PROXY`, `$HTTPS_PROXY`, and `$NO_PROXY`. Sim installs no global dispatcher, so every call that goes through the default `fetch` is proxied. The rest either build their own HTTP agent or speak a non-HTTP protocol. - + + This depends on the runtime. The published images run Bun. If you build the standalone output and run it under Node instead, Node ignores these variables unless started with `NODE_USE_ENV_PROXY=1` (Node 22.21+ / 24.5+), and the `fetch`-based "Yes" rows stop being proxied — model providers, Resend, Gmail sending, the desktop update feed, and the telemetry relay. The Azure Blob, GCS and Azure Communication Services rows still hold: those SDKs read the proxy variables through their own agents rather than through `fetch`. + + +| Outbound path | Honors `HTTP_PROXY` / `HTTPS_PROXY` | +|---|---| +| Model providers reached over the default `fetch` — Anthropic, OpenAI, Google/Gemini, Vertex, Groq, Cerebras, xAI, Mistral, DeepSeek, OpenRouter, Together, Fireworks, Ollama, LiteLLM, and the other OpenAI-compatible providers | Yes | +| Email via Resend, Azure Communication Services, and Gmail sending | Yes | +| The desktop update feed's calls to GitHub | Yes | +| Object storage — Azure Blob and GCS | Yes — their SDK pipelines read the proxy variables | +| Everything through the SSRF guard — the HTTP block, tools, connectors, outbound webhooks, content fetches, MCP servers | No | +| Azure OpenAI, Azure Anthropic, vLLM | Only when the endpoint comes from `AZURE_OPENAI_ENDPOINT`, `AZURE_ANTHROPIC_ENDPOINT`, or `VLLM_BASE_URL`. An endpoint typed into the block is validated and pinned to its resolved IP, which bypasses the proxy | +| Amazon Bedrock, and object storage on S3 | No — the AWS SDK uses its own request handler | +| Email via SMTP | No — Nodemailer opens a raw TCP connection | +| Email via Amazon SES | No — the AWS SDK transport, over HTTPS | +| OTLP export from the server SDK | No | +| The `/api/telemetry` relay that forwards browser events | Yes — it uses the default `fetch` | +| Postgres and Redis | No — raw TCP | + +The practical consequence: a mandatory-egress-proxy environment can route most LLM traffic, Resend mail, and Azure/GCS storage through the proxy, but guarded integration calls, S3, Bedrock, SMTP, telemetry, and datastore traffic still need a transparent proxy or NAT-based egress. + + + Set `NO_PROXY` for every destination that is not on the public internet, not just model endpoints. The app reaches the realtime server (`SOCKET_SERVER_URL`), the Presidio PII service (`PII_URL`), and itself (`INTERNAL_API_BASE_URL`) over the same default `fetch`, alongside self-hosted Ollama, LiteLLM, and vLLM — so a proxy that cannot reach your internal network breaks live updates and PII redaction, not only inference. + + Set it in the application environment, not your shell — under `app.env` on Helm, or the service's `environment:` on Compose. On Helm the suffixes alone are not enough: the chart wires `SOCKET_SERVER_URL`, `PII_URL`, and `OLLAMA_URL` to bare Service names, which no domain suffix matches. Add those names too — `helm template` prints the rendered ones, and the prefix is the release name unless it already contains `sim`, in which case it is the release name alone: + + ```yaml + # Helm — for a release named `acme` + app: + env: + NO_PROXY: "localhost,127.0.0.1,.svc,.svc.cluster.local,acme-sim-app,acme-sim-realtime,acme-sim-pii,acme-sim-ollama" + ``` + + ```yaml + # Docker Compose + services: + simstudio: + environment: + # Add any internal model service you run: ollama, litellm, vllm. + - NO_PROXY=localhost,127.0.0.1,simstudio,realtime,ollama + ``` + + +### The per-request escape hatch + +The HTTP block's `proxyUrl` is honored per request by the SSRF guard, which builds a proxy agent for that call instead of pinning the target IP. It applies only to that path — connectors, content fetches, and MCP calls take a different guarded transport with no per-request proxy option. + + + `proxyUrl` must be an `http://` URL **and** must resolve to a public address. The guard validates the proxy host under a dedicated `proxy` profile that has no operator allowlist, so `EGRESS_ALLOWED_HOSTS` and `EGRESS_ALLOWED_IP_RANGES` do not reach it. A corporate proxy on an RFC 1918 address is refused even when that range is allowlisted for everything else. See [Security](/platform/self-hosting/security#the-ssrf-boundary). + diff --git a/apps/docs/content/docs/platform/self-hosting/object-storage.mdx b/apps/docs/content/docs/platform/self-hosting/object-storage.mdx index 9df9f78731f..d4ba5189b80 100644 --- a/apps/docs/content/docs/platform/self-hosting/object-storage.mdx +++ b/apps/docs/content/docs/platform/self-hosting/object-storage.mdx @@ -6,7 +6,6 @@ description: Configure where Sim stores uploaded files — local disk, AWS S3, A import { Tab, Tabs } from 'fumadocs-ui/components/tabs' import { Callout } from 'fumadocs-ui/components/callout' import { Step, Steps } from 'fumadocs-ui/components/steps' -import { FAQ } from '@/components/ui/faq' Sim stores every uploaded file — knowledge base documents, chat attachments, execution outputs, profile pictures, and more — in object storage. Four backends are supported: @@ -17,8 +16,8 @@ Sim stores every uploaded file — knowledge base documents, chat attachments, e | **[Azure Blob](https://learn.microsoft.com/azure/storage/blobs/)** | Production on Azure | | **[Google Cloud Storage](https://cloud.google.com/storage)** | Production on GCP | - - Local disk writes to the container's `/uploads` directory. Files are lost when the container is recreated unless that path is on a persistent volume, and they are **not** shared across replicas. For any multi-replica or production deployment, use S3, Azure Blob, or Google Cloud Storage. + + Local disk writes to `/app/apps/sim/uploads` inside the container. Neither `docker-compose.prod.yml` nor the Helm chart mounts a volume there, so every uploaded file is lost the moment the container is recreated — and files are **not** shared across replicas. Mount your own volume at that path if you must stay on local disk; for any multi-replica or production deployment, use S3, Azure Blob, or Google Cloud Storage. ## How the backend is selected @@ -28,9 +27,11 @@ Set `STORAGE_PROVIDER` to `local`, `s3`, `azure`, or `gcs` to select a backend e 1. **Azure Blob** — used if `AZURE_STORAGE_CONTAINER_NAME` is set **and** either (`AZURE_ACCOUNT_NAME` + `AZURE_ACCOUNT_KEY`) or `AZURE_CONNECTION_STRING` is set. 2. **AWS S3** — used if `S3_BUCKET_NAME` **and** `AWS_REGION` are set (and Azure is not configured). 3. **Google Cloud Storage** — used if `GCS_BUCKET_NAME` is set (and neither Azure nor S3 is configured). -4. **Local disk** — the fallback when none is configured. +4. **Local disk** — used only when no cloud backend has been activated. Setting any of a backend's keys activates it, including a dedicated bucket, `S3_ENDPOINT`, or an Azure credential — so a half-configured backend fails at startup rather than quietly falling back here. -If `STORAGE_PROVIDER` is unset, Sim skips incomplete backends and uses the first ready match in that order. A higher-priority backend with its required fields present but invalid supplied values fails fast instead of silently falling through. An explicit `STORAGE_PROVIDER` takes precedence and must be valid and complete. +If `STORAGE_PROVIDER` is unset, Sim uses the first backend whose configuration is complete, in that order. An explicit `STORAGE_PROVIDER` takes precedence and must be valid and complete. + +A partially configured backend is skipped, so a fully configured one later in the order still wins. A backend whose values are present but malformed is different: it stops startup immediately, and a later fully configured one does not take over. What it never does is fall back to local disk: if no other backend is complete, startup fails with `File storage is partially or incorrectly configured`. `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` are checked as a pair — set both or neither. ## Set up AWS S3 @@ -40,15 +41,18 @@ If `STORAGE_PROVIDER` is unset, Sim skips incomplete backends and uses the first ### Create the buckets -Sim separates files into purpose-specific buckets. **Sim never creates buckets** — create each one yourself before configuring it. Only `S3_OG_IMAGES_BUCKET_NAME` and `S3_WORKSPACE_LOGOS_BUCKET_NAME` fall back to the general bucket; the others resolve to their own literal defaults, so set every bucket you intend to use. +Sim separates files into purpose-specific buckets. **Sim never creates buckets** — create each one yourself before configuring it. Most S3 bucket variables do **not** fall back to `S3_BUCKET_NAME`: leave `S3_KB_BUCKET_NAME`, `S3_CHAT_BUCKET_NAME`, `S3_COPILOT_BUCKET_NAME`, or `S3_PROFILE_PICTURES_BUCKET_NAME` unset and it resolves to an empty bucket name, so that file type fails to store. Three behave differently — `S3_OG_IMAGES_BUCKET_NAME` and `S3_WORKSPACE_LOGOS_BUCKET_NAME` fall back to `S3_BUCKET_NAME`, and `S3_EXECUTION_FILES_BUCKET_NAME` falls back to a literal default name (see the reference table below). ```bash # Set your region once export AWS_REGION=us-east-1 +# The eight purpose-specific buckets. The CORS and IAM steps below reuse this list. +export SIM_BUCKETS="workspace-files knowledge-base execution-files chat-files + copilot-files profile-pictures og-images workspace-logos" + # Create buckets (names must be globally unique — prefix with your org) -for name in workspace-files knowledge-base execution-files chat-files \ - copilot-files profile-pictures og-images workspace-logos; do +for name in ${SIM_BUCKETS:?run the export above first}; do aws s3api create-bucket \ --bucket "myorg-sim-$name" \ --region "$AWS_REGION" \ @@ -84,8 +88,7 @@ cat > /tmp/cors.json <<'EOF' } EOF -for name in workspace-files knowledge-base execution-files chat-files \ - copilot-files profile-pictures og-images workspace-logos; do +for name in ${SIM_BUCKETS:?run the export above first}; do aws s3api put-bucket-cors --bucket "myorg-sim-$name" --cors-configuration file:///tmp/cors.json done ``` @@ -153,7 +156,7 @@ S3_OG_IMAGES_BUCKET_NAME=myorg-sim-og-images S3_WORKSPACE_LOGOS_BUCKET_NAME=myorg-sim-workspace-logos ``` -Only `AWS_REGION` and `S3_BUCKET_NAME` are strictly required to switch Sim into S3 mode. Add the others so each file type lands in its own bucket. +`AWS_REGION` and `S3_BUCKET_NAME` are what switch Sim into S3 mode, but they do not cover the other file types — set every bucket above, because only the OpenGraph and workspace-logo buckets fall back to the general one. @@ -167,13 +170,13 @@ Only `AWS_REGION` and `S3_BUCKET_NAME` are strictly required to switch Sim into | `AWS_ACCESS_KEY_ID` | Access key | No (uses credential chain if unset) | | `AWS_SECRET_ACCESS_KEY` | Secret key | No (uses credential chain if unset) | | `S3_BUCKET_NAME` | General workspace files | **Yes** (enables S3) | -| `S3_KB_BUCKET_NAME` | Knowledge base documents | Recommended | -| `S3_EXECUTION_FILES_BUCKET_NAME` | Workflow execution files. Falls back to the literal name `sim-execution-files`, which you almost certainly do not own — always set this explicitly | **Yes** | -| `S3_CHAT_BUCKET_NAME` | Deployed chat assets | Recommended | -| `S3_COPILOT_BUCKET_NAME` | Copilot attachments | Recommended | -| `S3_PROFILE_PICTURES_BUCKET_NAME` | User avatars | Recommended | -| `S3_OG_IMAGES_BUCKET_NAME` | OpenGraph preview images (falls back to `S3_BUCKET_NAME`) | Optional | -| `S3_WORKSPACE_LOGOS_BUCKET_NAME` | Workspace logos (falls back to `S3_BUCKET_NAME`) | Optional | +| `S3_KB_BUCKET_NAME` | Knowledge base documents | **Yes**, to use knowledge bases (no fallback) | +| `S3_EXECUTION_FILES_BUCKET_NAME` | Workflow execution files. Falls back to the literal name `sim-execution-files`, which you almost certainly do not own — always set this explicitly | Effectively yes — the fallback is a bucket you do not own | +| `S3_CHAT_BUCKET_NAME` | Deployed chat assets | **Yes**, to use deployed chats (no fallback) | +| `S3_COPILOT_BUCKET_NAME` | Chat attachments | **Yes**, to attach files in Chat (no fallback) | +| `S3_PROFILE_PICTURES_BUCKET_NAME` | User avatars | **Yes**, for avatar uploads (no fallback) | +| `S3_OG_IMAGES_BUCKET_NAME` | OpenGraph preview images | Optional (falls back to `S3_BUCKET_NAME`) | +| `S3_WORKSPACE_LOGOS_BUCKET_NAME` | Workspace logos | Optional (falls back to `S3_BUCKET_NAME`) | | `S3_ENDPOINT` | Custom endpoint for S3-compatible storage (R2, MinIO, B2) | Optional (AWS S3 if unset) | | `S3_FORCE_PATH_STYLE` | `true` for path-style addressing (MinIO/Ceph) | Optional (defaults `false`) | @@ -188,7 +191,7 @@ Add the storage variables to the `.env` file used by `docker-compose.prod.yml`, docker compose -f docker-compose.prod.yml up -d ``` -Because files now live in S3, you no longer depend on a local `/uploads` volume for durability. +Because files now live in S3, nothing depends on the container's `/app/apps/sim/uploads` directory any more. @@ -232,6 +235,10 @@ AZURE_STORAGE_OG_IMAGES_CONTAINER_NAME=og-images AZURE_STORAGE_WORKSPACE_LOGOS_CONTAINER_NAME=workspace-logos ``` + + Set every container name. Like the S3 variables, the knowledge-base, chat, Chat-attachment, and profile-picture containers do not fall back to `AZURE_STORAGE_CONTAINER_NAME` — an unset one resolves to an empty container name and that file type fails to store. The OpenGraph and workspace-logo containers do fall back to it, and `AZURE_STORAGE_EXECUTION_FILES_CONTAINER_NAME` falls back to the literal name `sim-execution-files`. + + Direct browser uploads require a Blob service CORS rule on the storage account. Allow your exact Sim origin, `GET` and `PUT`, the `Content-Type` header, and the `x-ms-*` prefix used by signed blob and metadata headers. Small-file uploads also send `If-None-Match`; the signature itself is @@ -270,9 +277,12 @@ GCS uses one bucket per purpose, mirroring the S3 layout: export PROJECT_ID=your-project-id export LOCATION=us-central1 +# The eight purpose-specific buckets. The CORS and IAM steps below reuse this list. +export SIM_BUCKETS="workspace-files knowledge-base execution-files chat-files + copilot-files profile-pictures og-images workspace-logos" + # Create buckets (names must be globally unique — prefix with your org) -for name in workspace-files knowledge-base execution-files chat-files \ - copilot-files profile-pictures og-images workspace-logos; do +for name in ${SIM_BUCKETS:?run the export above first}; do gcloud storage buckets create "gs://myorg-sim-$name" \ --project "$PROJECT_ID" \ --location "$LOCATION" \ @@ -303,22 +313,20 @@ cat > /tmp/cors.json <<'EOF' "x-goog-meta-knowledgebaseid", "x-goog-meta-folderid", "x-goog-meta-workflowid", - "x-goog-meta-executionid", - "x-goog-meta-simuploadid" + "x-goog-meta-executionid" ], "maxAgeSeconds": 3600 } ] EOF -for name in workspace-files knowledge-base execution-files chat-files \ - copilot-files profile-pictures og-images workspace-logos; do +for name in ${SIM_BUCKETS:?run the export above first}; do gcloud storage buckets update "gs://myorg-sim-$name" --cors-file=/tmp/cors.json done ``` - Header names must be listed individually — GCS CORS matches `responseHeader` entries exactly and does not support wildcards like `x-goog-meta-*`. `ETag` is required because large-file multipart uploads read each part's `ETag` from the browser, and CORS hides the header otherwise. `x-goog-if-generation-match` is required by Sim's create-only signed uploads, which prevent a reused upload URL from replacing existing bytes. `x-goog-meta-simuploadid` carries the opaque receipt used to verify an upload after an ambiguous network response. + Header names must be listed individually — GCS CORS matches `responseHeader` entries exactly and does not support wildcards like `x-goog-meta-*`. `ETag` is required because large-file multipart uploads read each part's `ETag` from the browser, and CORS hides the header otherwise. `x-goog-if-generation-match` is required by Sim's create-only signed uploads, which prevent a reused upload URL from replacing existing bytes. @@ -332,8 +340,7 @@ Create a service account (or reuse the one your workload runs as) and grant it o ```bash gcloud iam service-accounts create sim-storage --project "$PROJECT_ID" -for name in workspace-files knowledge-base execution-files chat-files \ - copilot-files profile-pictures og-images workspace-logos; do +for name in ${SIM_BUCKETS:?run the export above first}; do gcloud storage buckets add-iam-policy-binding "gs://myorg-sim-$name" \ --member "serviceAccount:sim-storage@$PROJECT_ID.iam.gserviceaccount.com" \ --role roles/storage.objectAdmin @@ -375,7 +382,7 @@ GCS_OG_IMAGES_BUCKET_NAME=myorg-sim-og-images GCS_WORKSPACE_LOGOS_BUCKET_NAME=myorg-sim-workspace-logos ``` -Only `GCS_BUCKET_NAME` is strictly required to switch Sim into GCS mode. Every purpose-specific bucket falls back to the general bucket when unset — add the others so each file type lands in its own bucket. +`GCS_BUCKET_NAME` is what switches Sim into GCS mode and is the only bucket strictly required: every purpose-specific bucket falls back to it when unset. GCS bucket names are globally unique, so Sim uses no literal defaults here — not even for execution files. Set the dedicated buckets to separate the file types; leave them unset and everything lands in the general bucket. @@ -391,10 +398,10 @@ Only `GCS_BUCKET_NAME` is strictly required to switch Sim into GCS mode. Every p | `GCS_KB_BUCKET_NAME` | Knowledge base documents | Recommended (falls back to `GCS_BUCKET_NAME`) | | `GCS_EXECUTION_FILES_BUCKET_NAME` | Workflow execution files | Recommended (falls back to `GCS_BUCKET_NAME`) | | `GCS_CHAT_BUCKET_NAME` | Deployed chat assets | Recommended (falls back to `GCS_BUCKET_NAME`) | -| `GCS_COPILOT_BUCKET_NAME` | Copilot attachments | Recommended (falls back to `GCS_BUCKET_NAME`) | +| `GCS_COPILOT_BUCKET_NAME` | Chat attachments | Recommended (falls back to `GCS_BUCKET_NAME`) | | `GCS_PROFILE_PICTURES_BUCKET_NAME` | User avatars | Recommended (falls back to `GCS_BUCKET_NAME`) | -| `GCS_OG_IMAGES_BUCKET_NAME` | OpenGraph preview images (falls back to `GCS_BUCKET_NAME`) | Optional | -| `GCS_WORKSPACE_LOGOS_BUCKET_NAME` | Workspace logos (falls back to `GCS_BUCKET_NAME`) | Optional | +| `GCS_OG_IMAGES_BUCKET_NAME` | OpenGraph preview images | Recommended (falls back to `GCS_BUCKET_NAME`) | +| `GCS_WORKSPACE_LOGOS_BUCKET_NAME` | Workspace logos | Recommended (falls back to `GCS_BUCKET_NAME`) | A full Helm example (Workload Identity, GKE) lives at `helm/sim/examples/values-gcp.yaml`. @@ -406,7 +413,7 @@ Sim works with any S3-compatible store by pointing the S3 client at a custom end `S3_ENDPOINT` is trusted operator configuration, so it is used as-is — `http://` and private hosts are accepted (no SSRF/HTTPS gate). Don't wire it to untrusted input. - + **The endpoint must be reachable from your users' browsers, and the bucket needs CORS.** Uploads use presigned `PUT` requests sent **directly from the browser** to `S3_ENDPOINT` (downloads are proxied back through the app, so they only need server-side reachability). This means: - A purely internal endpoint (e.g. `https://minio.internal:9000` that only the app pods can resolve) will let the server start cleanly but **uploads will fail in the browser**. Use an endpoint your users can reach. @@ -482,7 +489,7 @@ state that outlives its database row: The provider window should exceed the 24-hour upload-session lifetime so an in-progress completion can still recover. Do not add an object-expiration rule for final upload keys. - + Object expiration and incomplete-multipart cleanup are different lifecycle operations. Configure the incomplete-multipart operation; expiring objects does not remove abandoned multipart parts. @@ -496,7 +503,3 @@ After restarting with the new configuration: 3. Reload the page — the file should still render (downloads stream back through the app at `/api/files/serve`). If uploads fail, check the app logs for credential or permission errors (see [Troubleshooting](/platform/self-hosting/troubleshooting)). - - diff --git a/apps/docs/content/docs/platform/self-hosting/observability.mdx b/apps/docs/content/docs/platform/self-hosting/observability.mdx index cc9d1faf41c..1e229fe1a69 100644 --- a/apps/docs/content/docs/platform/self-hosting/observability.mdx +++ b/apps/docs/content/docs/platform/self-hosting/observability.mdx @@ -4,7 +4,6 @@ description: Health checks, logs, tracing, and what to alert on --- import { Callout } from 'fumadocs-ui/components/callout' -import { FAQ } from '@/components/ui/faq' ## Health endpoints @@ -81,53 +80,78 @@ See [Security](/platform/self-hosting/security) for the `INTERNAL_API_BASE_URL` ## Anonymous telemetry - - **Sim sends anonymous usage telemetry by default.** OpenTelemetry traces are exported to `https://telemetry.simstudio.ai/v1/traces` unless you turn it off. Self-hosted deployments with an egress policy should decide about this explicitly. - +Whether anonymous telemetry runs depends on how you deploy. A Docker Compose or source deployment sends it unless you turn it off, so a deployment with an egress policy should decide about this deliberately rather than inherit the default. -What is collected, per `apps/sim/telemetry.config.ts`: feature-usage statistics, error rates, performance metrics (sampled at 10%), and AI/LLM operation traces. What is **not** collected: personal information, workflow content or outputs, API keys or tokens, and IP addresses or geolocation. +Telemetry has two halves that are decided at different times. -Three ways to change it: +| Half | When it is decided | Default | +|---|---|---| +| Server SDK | Runtime | **Off** on Helm (`app.envDefaults` sets `NEXT_TELEMETRY_DISABLED: "1"`); **on** for Docker Compose and source runs, which set nothing | +| Browser | `next build` | **Off** in every published image — the value is inlined at build time, and no runtime variable changes it | -```bash -# Disable entirely -NEXT_TELEMETRY_DISABLED=1 +Neither half gates `POST /api/telemetry`, the relay that forwards browser events. See the warning below. -# Or redirect to your own OTLP collector instead of Sim's -TELEMETRY_ENDPOINT=http://otel-collector.observability.svc.cluster.local:4318/v1/traces -``` +When the server SDK runs it exports three OTLP signals to the same base endpoint: traces at `/v1/traces`, metrics at `/v1/metrics`, and application **log records** at `/v1/logs`. Sim's own telemetry is scoped to feature-usage statistics, error rates, performance metrics, and AI/LLM operation traces — not workflow content or outputs, API keys, or geolocation. + +The log stream is different in kind: it carries every line at or above `LOG_LEVEL` with its structured metadata, error messages and stack traces, which can include user ids, emails and URLs. That matters if you raise `LOG_LEVEL` as suggested above, and it is a reason to point the exporter at your own collector rather than leaving it at the default. + +`NEXT_TELEMETRY_DISABLED=1` stops the server's OpenTelemetry SDK, which returns before it initializes, so it ends the app's own OTLP export to any endpoint. It does not affect Trigger.dev task telemetry, configured separately below. The **Settings → Privacy → Allow anonymous telemetry** toggle is browser-side only and does not stop server spans. + + + On Helm, `NEXT_TELEMETRY_DISABLED` is what you must **clear** before any tracing works — including tracing to your own collector. Override it with `null` to remove the key entirely. -Users can also toggle it off individually under **Settings → Privacy → Allow anonymous telemetry**. + ```yaml + app: + envDefaults: + NEXT_TELEMETRY_DISABLED: null + ``` + ## Tracing -Sim emits OpenTelemetry traces. The chart can also deploy a collector for you: +The chart can deploy an OpenTelemetry Collector for you: ```yaml telemetry: enabled: true ``` + + `telemetry.enabled: true` deploys the collector and injects `OTEL_EXPORTER_OTLP_ENDPOINT` pointing at it, but the app still has `NEXT_TELEMETRY_DISABLED: "1"` from `app.envDefaults`. The collector runs and receives nothing until you clear that key as shown above. Enabling telemetry is two changes, not one. + + Or point the app at a collector you already run: | Variable | Purpose | |---|---| -| `OTEL_EXPORTER_OTLP_ENDPOINT` | Collector endpoint (OTLP) | +| `OTEL_EXPORTER_OTLP_ENDPOINT` | Collector endpoint (OTLP over HTTP) | | `OTEL_EXPORTER_OTLP_HEADERS` | Auth headers, `key=value` comma-separated | -| `OTEL_TRACES_SAMPLER_ARG` | Sampling ratio | +| `TELEMETRY_ENDPOINT` | Legacy endpoint variable, still honored | +| `TELEMETRY_SAMPLING_RATIO` | Sampling ratio, `0`–`1` | +| `OTEL_TRACES_SAMPLER_ARG` | Sampling ratio, used only when `TELEMETRY_SAMPLING_RATIO` is unset | | `OTEL_DEPLOYMENT_ENVIRONMENT` | Environment label on emitted spans | -| `TELEMETRY_SAMPLING_RATIO` | Application-level sampling ratio | -| `TELEMETRY_ENDPOINT` | Custom telemetry endpoint | -For Grafana Cloud specifically: +The endpoint is resolved by precedence, first match wins: `OTEL_EXPORTER_OTLP_ENDPOINT`, then `TELEMETRY_ENDPOINT`, then the endpoint in `apps/sim/telemetry.config.ts`. This precedence governs the server SDK only — the `/api/telemetry` relay reads `TELEMETRY_ENDPOINT` and nothing else. Sampling resolves the same way: `TELEMETRY_SAMPLING_RATIO`, then `OTEL_TRACES_SAMPLER_ARG`, defaulting to 1.0. Editing `telemetry.config.ts` only changes the last fallback, so an environment variable set anywhere in your stack silently wins over it. + + + `OTEL_SERVICE_NAME` is not read. The chart sets it to `sim-app` when `telemetry.enabled` is true, but the app hardcodes its own service name, so spans arrive under the literal value `mothership`. Filter on that, not on `sim-app`. + + +If you enable the chart's Jaeger export, point `telemetry.jaeger.endpoint` at Jaeger's **OTLP gRPC port (4317)** — the collector exports over OTLP. + +### Trigger.dev task telemetry + +`GRAFANA_OTLP_ENDPOINT`, `GRAFANA_OTLP_HEADERS`, and `GRAFANA_DEPLOYMENT_ENVIRONMENT` are read only by `apps/sim/trigger.config.ts`. They route **Trigger.dev background-task** telemetry to Grafana Cloud and have no effect on the app's own OTLP export — for that, use `OTEL_EXPORTER_OTLP_ENDPOINT` above. | Variable | Purpose | |---|---| -| `GRAFANA_OTLP_ENDPOINT` | Grafana OTLP endpoint | +| `GRAFANA_OTLP_ENDPOINT` | Grafana OTLP gateway base URL | | `GRAFANA_OTLP_HEADERS` | e.g. `Authorization=Basic ` | | `GRAFANA_DEPLOYMENT_ENVIRONMENT` | Deployment tier label | -If you enable the chart's Jaeger export, point `telemetry.jaeger.endpoint` at Jaeger's **OTLP gRPC port (4317)** — the collector exports over OTLP. + + All three or none. Setting one or two throws at startup with a message naming them, so a partially configured worker will not boot. + ## Metrics @@ -137,12 +161,16 @@ If you enable the chart's Jaeger export, point `telemetry.jaeger.endpoint` at Ja Until an application metrics endpoint ships, build alerting from the signals that do exist: -- **Kubernetes state** — pod restarts, `CrashLoopBackOff`, OOMKills, replica count vs desired, PVC utilization (kube-state-metrics). +- **Kubernetes state** — pod restarts, `CrashLoopBackOff`, OOMKills, replica count vs desired, PVC utilization (kube-state-metrics). For a point-in-time read on resource pressure, `kubectl top pods -n simstudio` shows current CPU and memory usage — compare it against the limits from `kubectl describe pod`. It is the first thing to check when a pod is being OOMKilled. - **Ingress/load balancer** — request rate, 5xx rate, p99 latency, websocket connection count. - **PostgreSQL** — connection count vs `max_connections`, replication lag, disk usage, long-running queries. - **Redis** — memory usage, evictions, connected clients. - **CronJobs** — last successful completion per job. +### CloudWatch metrics + +Sim's hosted-key metrics recorder arms itself whenever `AWS_ACCESS_KEY_ID` is present, but every call site is additionally gated on the deployment being Sim Cloud. A self-hosted deployment emits no `PutMetricData` calls, even with AWS credentials configured for S3. If you want defence in depth against that gate ever changing, deny `cloudwatch:PutMetricData` on the IAM identity behind those credentials. + ## What to alert on | Alert | Why it matters | @@ -159,29 +187,3 @@ Until an application metrics endpoint ships, build alerting from the signals tha The CronJob alert is the one most deployments lack and most need. Background job failures produce no user-visible error — schedules simply stop firing. Alert on `kube_cronjob_status_last_successful_time` lagging, with a per-job threshold derived from that job's schedule. - -## Quick diagnosis - -```bash -# Overall state -kubectl get pods,cronjobs -n simstudio - -# Why is a pod unhealthy -kubectl describe pod -n simstudio - -# Did migrations succeed -kubectl logs -n simstudio deploy/sim-app -c migrations --tail=100 - -# Are background jobs running -kubectl get jobs -n simstudio --sort-by=.metadata.creationTimestamp | tail - -# Resource pressure -kubectl top pods -n simstudio -``` - - diff --git a/apps/docs/content/docs/platform/self-hosting/platforms.mdx b/apps/docs/content/docs/platform/self-hosting/platforms.mdx index d2dd3eb601a..1c00e330543 100644 --- a/apps/docs/content/docs/platform/self-hosting/platforms.mdx +++ b/apps/docs/content/docs/platform/self-hosting/platforms.mdx @@ -3,7 +3,6 @@ title: Cloud Platforms description: Provider-specific notes for running Sim on Railway, a VPS, or managed Kubernetes --- -import { Tab, Tabs } from 'fumadocs-ui/components/tabs' import { Callout } from 'fumadocs-ui/components/callout' This page covers what differs per provider. The deployment itself is the same everywhere — follow [Docker](/platform/self-hosting/docker) for a single node or [Kubernetes](/platform/self-hosting/kubernetes) for a cluster. diff --git a/apps/docs/content/docs/platform/self-hosting/redis.mdx b/apps/docs/content/docs/platform/self-hosting/redis.mdx index 99f55957ced..45170c0511f 100644 --- a/apps/docs/content/docs/platform/self-hosting/redis.mdx +++ b/apps/docs/content/docs/platform/self-hosting/redis.mdx @@ -5,7 +5,6 @@ description: When Redis is optional, when it is required, and how to configure i import { Tab, Tabs } from 'fumadocs-ui/components/tabs' import { Callout } from 'fumadocs-ui/components/callout' -import { FAQ } from '@/components/ui/faq' Sim uses Redis as a message bus and shared cache. Both deployments ship it by default — Docker Compose as a `redis` service, Helm as a `redis` Deployment — so this page is mostly about when to replace the bundled instance with a managed one, and what breaks if Redis is absent entirely. @@ -13,7 +12,7 @@ Sim uses Redis as a message bus and shared cache. Both deployments ship it by de | Use | Without Redis | |---|---| -| **Pub/sub** — live Chat task status, table events, execution cancellation, MCP tool-change notifications, copilot tool confirmations | Falls back to a **process-local** emitter: events never leave the pod that produced them | +| **Pub/sub** — live Chat task status, table events, execution cancellation, MCP tool-change notifications, Chat tool confirmations | Falls back to a **process-local** emitter: events never leave the pod that produced them | | **Socket.IO adapter** (realtime) | Collaboration events are not delivered across realtime pods | | Idempotency store | Falls back to PostgreSQL | | Execution progress markers | Falls back to PostgreSQL | @@ -25,6 +24,8 @@ Sim uses Redis as a message bus and shared cache. Both deployments ship it by de With more than one app or realtime replica and no `REDIS_URL`, users on different pods stop seeing each other's edits and live status updates. Beyond one startup log line noting single-pod mode, nothing is logged — the app looks healthy and quietly loses events. Treat Redis as mandatory the moment `replicaCount` exceeds 1. +Everything Sim keeps in Redis is cache, coordination state, or an in-flight event — never committed data, which lives in PostgreSQL and object storage. Persistence is therefore not required. Losing or restarting the instance is not free, though: cancellation markers and the cross-pod half of execution streaming live here, so active runs stop streaming and a cancellation issued across the gap may not land. Completed work is unaffected. + ## Configuration ```bash @@ -106,10 +107,3 @@ docker compose -f docker-compose.prod.yml exec redis redis-cli ping # PONG The functional test: open the same workflow in two browser windows served by different replicas and confirm edits appear in both. With a single replica this always passes, so scale to two before testing. Watch the app logs at startup for `Redis` connection errors — a wrong password or unreachable host is logged there. - - diff --git a/apps/docs/content/docs/platform/self-hosting/reference-architectures.mdx b/apps/docs/content/docs/platform/self-hosting/reference-architectures.mdx new file mode 100644 index 00000000000..0f3242e5995 --- /dev/null +++ b/apps/docs/content/docs/platform/self-hosting/reference-architectures.mdx @@ -0,0 +1,177 @@ +--- +title: Reference Architectures +description: What you provision and what the chart provisions, per cloud, before you run helm install +--- + +import { Tab, Tabs } from 'fumadocs-ui/components/tabs' +import { Callout } from 'fumadocs-ui/components/callout' + +Sim ships a Helm chart, not infrastructure-as-code. You bring a Kubernetes cluster and, in production, managed Postgres and object storage; the chart installs Sim on top. This page draws that line precisely so you can map it onto whatever Terraform, Bicep, or CloudFormation you already run, and decide what to provision before you reach [Kubernetes](/platform/self-hosting/kubernetes). + + + There is no official Terraform module or CloudFormation template. The chart is the deployment interface, and it is what your own IaC should call — `helm_release` in Terraform, or a Helm task in your pipeline. Everything below describes the inputs that chart expects. + + +## The boundary + +Everything the chart can run itself, it runs by default. That is convenient for evaluation and wrong for production, because two of those defaults keep state. + +| Component | Chart default | Production | +|---|---|---| +| App, realtime, migrations | Deployed | Deployed by the chart | +| Scheduled jobs (CronJobs) | Deployed | Deployed by the chart | +| PostgreSQL | Deployed in-cluster | **Replace** with managed Postgres — set `externalDatabase.*` and `postgresql.enabled: false` | +| Redis | Deployed in-cluster | Replace with a managed cache, or keep the bundled one — it holds no committed data | +| Object storage | None — local disk | **Required.** S3, Azure Blob, or GCS. Local disk is lost when a container is recreated and is not shared across replicas | +| Ingress | Off | You install the controller; the chart renders the Ingress | +| TLS certificates | Off | You provision them | +| Remote sandbox | Off | Required for Python, Shell, and imported JavaScript — a provider **and** an immutable Function image. See [Security](/platform/self-hosting/security) | +| Pi execution | Off | A separate image from the Function one, set with `E2B_PI_TEMPLATE_ID` (a template name, an alias, or an immutable `