Skip to content

StScript assistant

stscript.assistant is a capability of the AI chat: a server-side behavior profile for the script editor. It creates indicators and robots from a description, explains code or a selected fragment, analyzes errors and logic, and proposes changes to the open document.

It is not a separate chat service and not a separate provider. The same methods are used — AIChatCreateConversation, AIChatSendMessage, AIChatGetRun — with a few new fields, plus two new methods: AIGetCapabilities and AIChatGetArtifact. A chat that does not send the new fields works exactly as before.

What it does not do. It never saves, runs or publishes a script, never enables autostart and never trades. There is no backtest, no profit estimate and no parameter optimization. Checking code on the server calls no trading method of the platform.

How a request flows

Editor                         AI module                                   Model
  │ AIGetCapabilities ──────────▶ runtimeId, referenceVersion, limits
  │ AIChatCreateConversation ──▶ conversation with capability
  │ AIChatSendMessage ─────────▶ checks: capability, schema, size, hash,
  │   (editorContext snapshot)   runtime, selection, declared kind
  │ ◀──────────────────────────  runId (queued)          snapshot saved
  │                              reference search ──────────────────────▶
  │ ◀── ai.run.progress ───────  candidate ◀────────────────────────────── code
  │                              parse → validate → compile → policy
  │                              diagnostics ───────────────────────────▶ repair
  │                              final check of the exact source
  │ ◀── ai.run.completed         artifact + message saved
  │ AIChatGetRun ──────────────▶ text, artifact descriptor, analysis
  │ AIChatGetArtifact ─────────▶ base + proposal + validation report
  │ diff, local re-check, apply with undo (by the user)

1. Discover the capability

Call AIGetCapabilities once per session. Offer the mode only when the capability is enabled, and send the runtimeId and referenceVersion it returns with every request.

runtimeId identifies one immutable build of the StScript parser, validator, compiler and standard library — the fingerprint of ion-interface/src/app/core/dsl/runtimeIdentity.js (node scripts/stscript-runtime-identity.mjs prints it):

  1. take the pairs [filename, source] for core.js, stdlib.js, stdlib_math.js, stdlib_ta.js, stdlib_ui.js, validator.js, in exactly this order;
  2. read each source as UTF-8 and normalize CRLF and CR to LF;
  3. SHA-256 over the UTF-8 bytes of JSON.stringify(entries), lowercase hex;
  4. prefix stscript-dsl-sha256:.

The server computes the same value for the compiler package it checks with and publishes it in discovery. If the editor ships another build, the server answers AI_STSCRIPT_RUNTIME_UNSUPPORTED instead of silently checking with another version, and the editor keeps sending source blocked until discovery confirms its exact runtime.

referenceVersion identifies the language reference the assistant uses. Both values are opaque: never build them by hand.

2. Create a conversation

{
  "title": "EMA indicator",
  "accountLogin": 7777864,
  "capability": "stscript.assistant",
  "protocolVersion": 1
}

The server picks the agent of the brand and audience that supports the capability; the interface does not keep an agentId. The capability is fixed for the conversation: a regular chat never turns into a code session, and a code session does not accept questions without a snapshot. Use a separate conversation for each.

AIChatGetConversations accepts capability to list only editor conversations (stscript.assistant) or only regular ones (none).

3. Send a request with the editor snapshot

Every new question carries the current editor state, also for an empty document. Old code from the history never replaces it. If the user turned off sharing code with the assistant, do not send the request at all: the server never reads saved scripts behind the user's back.

{
  "conversationId": "conv_…",
  "requestId": "ef731e89-89e0-42d5-b806-316aad662a23",
  "accountLogin": 7777864,
  "content": "Add a second moving average with period 50",
  "route": "/en/platform/scripteditor",
  "capability": "stscript.assistant",
  "protocolVersion": 1,
  "operation": "modify",
  "ui": {
    "language": "en",
    "platform": "desktop",
    "supportedActions": ["stscript.editor.proposal.open"]
  },
  "editorContext": {
    "schema": 1,
    "runtimeId": "stscript-dsl-sha256:3de9a0c52570c0657861ab27d2ca22d3e46a59015e87043ae066c270cc236c99",
    "referenceVersion": "reference-…",
    "documentId": "a3cb73aa-f567-4105-b080-14cb172ef1c9",
    "documentVersion": 12,
    "scriptId": 42,
    "language": "stscript",
    "kind": "indicator",
    "name": "EMA",
    "source": "script(\"EMA\", kind=\"indicator\")\ninput length = 20\nma = ta.ema(close, length)\nplot(ma)\n",
    "sourceHash": "sha256:…",
    "selection": { "startLineNumber": 3, "startColumn": 1, "endLineNumber": 3, "endColumn": 27 },
    "diagnostics": []
  }
}

Operations

operation What the user wants Proposal
auto the assistant decides from the message, the code, the selection and the diagnostics; the document may be empty only when the user asks to create or change code
generate a new script from a description; the document may be empty full new source
explain an explanation of the code or of the selection none
analyze errors and possible logic problems none; findings in analysis
fix correct the errors with minimal changes full corrected source
modify change the code as asked full new source

When the task is ambiguous the assistant may answer with a clarifying question and no proposal.

Send auto when discovery lists it. The user does not choose an action; the assistant decides it again for every message, and a question about the code never forces a change of the code. What the answer actually did is returned as result.resolvedOperation (generate, modify or fix when there is a proposal, otherwise explain or analyze) and stored as the operation of the artifact. An explicit operation, including modify sent by a client that does not know auto, is a hint as well: the user's message decides, and a question gets an answer without a proposal.

editorContext

The schema is strict: an unknown field is rejected. The JSON Schema is published with the module (skills/stscript-assistant/contracts/editor-context.schema.json in sct-ai).

Field Rule
schema 1
runtimeId, referenceVersion exactly the values from AIGetCapabilities
documentId UUID of the editor tab or document
documentVersion integer that grows on every change of the source. Correlation only — not a saved revision and not an authorization
scriptId id of the saved script, or null for an unsaved document
language stscript
kind indicator or robot. For existing code the script(...) declaration wins; a contradiction is AI_STSCRIPT_KIND_MISMATCH
name document name, up to 255 characters
source the current unsaved code. Empty only for generate
sourceHash hash of source, see below
selection a Monaco range or null. The server derives the selected text from source itself
diagnostics optional, up to 100 diagnostics of the editor. Treated as untrusted hints

content stays the task description with its usual limit of 8 000 characters. Never put the source into content.

Hash and positions

These rules must match byte for byte on both sides, otherwise every request fails with "hash does not match":

  • Normalize line endings to LF (\r\n and \r become \n) before hashing. Nothing else: no trim, no Unicode normalization, no added final newline.
  • sourceHash = "sha256:" + lowercaseHex(SHA-256(UTF-8(source))).
  • The source limit is 64 000 UTF-16 code units — string.length in JavaScript, where an emoji counts as two. The request body is limited to 1 MiB.
  • Positions follow Monaco: lines and columns start at 1, columns are UTF-16 code units, the end of a range is exclusive. null means the parser gave no position; the server never invents one.

skills/stscript-assistant/contracts/fixtures/golden.json in sct-ai contains hash fixtures (empty string, CRLF, lone CR, trailing spaces, Cyrillic, emoji, combining marks) and a corpus of sources with the expected check result. Run the editor validator over the same corpus to make sure both sides agree on status, kind, inputs and positions.

Idempotency

The answer is the usual { runId, messageId, state, idempotent }. Repeating the same requestId with the same normalized payload in the same session, account and conversation returns the existing run — retry after a timeout with the same key. The same key with a different source, operation or text is AI_REQUEST_ID_CONFLICT, not a new answer.

4. Follow the run

The usual events arrive — ai.run.queued, started, delta, tool.started, tool.completed, completed, cancelled, failed — plus ai.run.progress for the stages of the code check:

{
  "event": "module:event",
  "type": "ai.run.progress",
  "data": {
    "runId": "run_…",
    "conversationId": "conv_…",
    "sequence": 8,
    "phase": "validating",
    "attempt": 1,
    "maxAttempts": 3
  }
}

phase is one of retrieving, generating, validating, repairing, finalizing. While the run is active, AIChatGetRun returns the same object as progress. Events never carry the source: after completed, read the run and the artifact. Text deltas are an explanation for the user, not an instruction to apply anything; the final result.content is authoritative because the explanation may change after the check.

5. Read the result

result of AIChatGetRun keeps its usual fields and adds:

{
  "capability": "stscript.assistant",
  "protocolVersion": 1,
  "operation": "auto",
  "resolvedOperation": "modify",
  "content": "Added EMA 50. Review the changes before applying.",
  "artifacts": [{
    "id": "art_…",
    "type": "stscript.source_proposal",
    "schema": 1,
    "title": "EMA",
    "kind": "indicator",
    "validationStatus": "passed",
    "canApply": true,
    "sourceHash": "sha256:…"
  }],
  "analysis": {
    "schema": 1,
    "summary": "Added a second EMA without changing the first.",
    "findings": [],
    "referenceIds": ["ta.ema", "ui.plot"]
  },
  "actions": [{
    "id": "act_…",
    "type": "stscript.editor.proposal.open",
    "version": 1,
    "mode": "button",
    "risk": "low",
    "label": "Review changes",
    "labelKey": "ai.stscript.reviewChanges",
    "payload": { "artifactId": "art_…" },
    "idempotencyKey": "run_…:stscript.editor.proposal.open:art_…"
  }],
  "sources": [{ "id": "ta.ema", "title": "ta.ema — exponential moving average", "type": "stscript.reference", "referenceVersion": "reference-…" }]
}
  • One artifact at most per answer. The full code is loaded with AIChatGetArtifact; the run and the history carry only the descriptor.
  • The kind of a proposal comes from its own script(...) declaration, not from the open document: a robot proposed for an open indicator is checked by the robot validator and the robot policy, and base.kind keeps the kind of the document.
  • canApply means the server accepted the proposal: it passed parse, validate, compile and policy for exactly this sourceHash. It is not a guarantee that the editor still holds the base code.
  • When no candidate passed after the repairs, the run is still completed: the artifact has validationStatus: "failed", canApply: false and no action, and the text explains what is wrong. A technical failure of the model or of the validator, or a timeout, is failed with an errorCode.
  • analysis.findings are logic remarks of the assistant (origin: "ai", phase: "analysis") in the diagnostic format. They are never mixed with compiler errors.
  • The action stscript.editor.proposal.open only opens the comparison. It is returned only when ui.supportedActions declares it, and only for an applicable proposal. The artifact can be read without it: a client that sends no ui (for example with settings.ai.sendUIContext = false) opens the descriptor from result.artifacts itself. The first editor client does not execute AI actions at all and works from the descriptor.

History (AIChatGetConversation) returns the descriptors in message.metadata.artifacts and the analysis in message.metadata.analysis. The user message keeps metadata.editor — document id, version, kind, hash and size of the snapshot, not the code. Polling and reconnecting return the same artifact ids; nothing is generated again.

6. Apply a proposal

The frontend is responsible for these steps:

  1. Show which document the chat is attached to and that its code is sent.
  2. Send the current snapshot with every request and increase documentVersion on every edit.
  3. Render the text, the diagnostics, the reference links and the artifact descriptor.
  4. Load the full artifact and build the difference from base.source and proposal.source. The first release always returns the full new source — no patches.
  5. Before applying, check the account, documentId, runtimeId, that the editor still holds base.sourceHash, and that proposal.source hashes to proposal.sourceHash. Re-check the proposal with the local validator.
  6. If the code changed meanwhile, show a conflict and offer a new request with the current snapshot. There is no automatic merge.
  7. Apply only by an explicit user action, as one Monaco edit that can be undone; this increases the document version.
  8. Do not apply the same artifact to the same version twice, and never run actions from the history automatically.
  9. Save and Run stay separate editor operations. Changing the code does not change a robot that is already running.

An empty document follows the same rules: scriptId: null does not allow applying a proposal to any other open tab.

Diagnostics

{
  "code": "STSCRIPT_PARSE_ERROR",
  "severity": "error",
  "origin": "server",
  "phase": "parse",
  "message": "Unexpected token '/' at start of statement",
  "range": { "startLineNumber": 2, "startColumn": 1, "endLineNumber": 2, "endColumn": 2 },
  "referenceIds": ["language.syntax"]
}
Field Values
severity error, warning, info
origin client, server, ai
phase parse, validate, compile, policy, runtime, analysis
range Monaco range or null
referenceIds only ids of the reference of the current referenceVersion

Codes of the server check:

Code Phase Meaning
STSCRIPT_EMPTY_SOURCE parse no code
STSCRIPT_PARSE_ERROR parse the parser rejected the code; the range points into the original source
STSCRIPT_DECLARATION_ERROR validate missing or wrong script(...) declaration
STSCRIPT_KIND_MISMATCH validate the declared kind differs from the expected one
STSCRIPT_VALIDATION_ERROR validate unknown function or variable, assignment to an input, a reserved name, trade.*/log outside an event handler, a repeated handler, a loop or syntax tree over the runtime limits, … (no position). The message keeps the runtime code, e.g. SCRIPT_LOOP_LIMIT, SCRIPT_AST_LIMIT, SCRIPT_ACTION_REQUIRES_EVENT
STSCRIPT_COMPILE_ERROR compile the compiler rejected the code, for example a UI call in a robot
STSCRIPT_POLICY_DRYRUN_REQUIRED policy a robot without input dryRun = 1
STSCRIPT_POLICY_LIVE_TRADING policy dryRun or simulate switched off
STSCRIPT_POLICY_AUTOSTART policy input autostart enabled
STSCRIPT_POLICY_POSITION_SCOPE policy positionScope widened to account
STSCRIPT_POLICY_AST_LIMIT policy more than 10 000 syntax nodes
STSCRIPT_CHECK_LIMIT parse the check exceeded 5 s or 256 MiB

Validation report

{
  "schema": 1,
  "runtimeId": "stscript-dsl-sha256:3de9a0c52570c0657861ab27d2ca22d3e46a59015e87043ae066c270cc236c99",
  "referenceVersion": "reference-…",
  "sourceHash": "sha256:…",
  "status": "passed",
  "checks": { "parse": "passed", "validate": "passed", "compile": "passed", "policy": "passed" },
  "execution": { "status": "not_run", "reason": "NOT_ENABLED" },
  "diagnostics": [],
  "limitations": ["NO_BACKTEST", "NO_LIVE_EXECUTION"]
}

A check is passed, failed or not_run; the overall status is passed, failed or unavailable. passed requires parse, validate, compile and policy to pass for exactly sourceHash. The code is never executed in this release, which execution states explicitly.

How the server checks code

  • The check uses the same parser, validator and compiler as the editor, taken from ion-interface and pinned by runtimeId. There is no second parser and no check by regular expressions or by the model's word.
  • The path repeats the editor: parse → mandatory script(...) declaration → validation for the declared kind → compile.
  • Each check runs in a separate process with no environment, no credentials and no network connections of the service, limited to 5 seconds and 256 MiB. The compiled function is constructed to verify it, never called.
  • The policy is checked on the syntax tree, not by searching strings: a robot proposal must declare input dryRun = 1, must not switch dryRun or simulate off, must not enable autostart and must not widen positionScope to account. Existing live code can be explained and analyzed, but an applicable proposal never keeps dryRun = 0.
  • After the model's repairs the final source is checked again, independently of what the model reported. Up to three candidates per answer: the first plus two repairs.

Errors

New request errors keep the usual body { "error": "CODE", "message": "…" }. Some codes also carry structured data of the module error — reason for AI_CAPABILITY_DISABLED, requested and supported for AI_STSCRIPT_RUNTIME_UNSUPPORTED, expectedSourceHash for a hash mismatch; whether it reaches the client as details depends on the platform router. The interface must work from the code alone.

HTTP Code Meaning
400 AI_STSCRIPT_CONTEXT_REQUIRED no editorContext
400 AI_STSCRIPT_CONTEXT_INVALID wrong schema, operation, range, hash, or an empty source for an operation that needs code
400 AI_STSCRIPT_KIND_MISMATCH kind contradicts the declaration of the existing code
403 AI_CAPABILITY_DISABLED the capability is not available in this scope (details.reason)
404 AI_ARTIFACT_NOT_FOUND the artifact does not exist or is not available to this session
409 AI_CAPABILITY_MISMATCH the request does not match the conversation's capability or protocol
409 AI_REQUEST_ID_CONFLICT the same requestId with a different payload or from another scope
409 AI_STSCRIPT_RUNTIME_UNSUPPORTED no exactly matching compiler and reference (details.supported)
413 AI_STSCRIPT_SOURCE_TOO_LARGE the source or the request body exceeds the limit
429 AI_RATE_LIMITED another code request of the session is running, or an hourly quota is reached
503 AI_STSCRIPT_VALIDATOR_UNAVAILABLE the check cannot be performed now

Existing errors such as AI_CONVERSATION_BUSY, AI_BUDGET_EXCEEDED and AI_ACCESS_DENIED keep their meaning. Wrong code in a fix or analyze request is the task itself, not a request error.

After a run is accepted, failures arrive in errorCode / errorMessage of the run: AI_STSCRIPT_VALIDATOR_UNAVAILABLE, AI_OUTPUT_INVALID (the model did not keep the format), AI_OUTPUT_TRUNCATED (the model ran out of output tokens; a partial script is never proposed), AI_RUN_TIMEOUT, AI_CONTEXT_TOO_LARGE, AI_STSCRIPT_RUNTIME_UNSUPPORTED. Reading the run still answers 200.

Limits

Limit Default
Run time 180 s
Repairs after the first candidate 2 (3 candidates)
Artifacts per run 1
Tool calls per run 12
Source 64 000 UTF-16 code units
Request body 1 MiB
Client diagnostics 100
One check 5 s, 256 MiB
Active code requests per session 1, plus hourly quotas per session and per account

All limits are configurable in the [stscript] section of the module configuration. A brand that does not agree to send source code to external model providers can switch the capability off for itself; it then reports enabled: false.