[{"data":1,"prerenderedAt":58},["ShallowReactive",2],{"doc:en:plugins\u002Fapi":3},{"title":4,"lede":5,"html":6,"headings":7},"The protocol","A plugin with a program talks to Escouade in JSON-RPC 2.0: inside the app, in WASM, if it is in JS, TypeScript or Rust; on its standard input and output if it is a process. This page describes the messages and the process.","\u003Cp>A \u003Ccode class=\"key\">run.js\u003C\u002Fcode> or \u003Ccode class=\"key\">run.wasm\u003C\u002Fcode> plugin speaks the same protocol: \u003Ca href=\"#a-program-in-wasm\">what changes in WASM\u003C\u002Fa> comes below, and \u003Ca href=\"\u002Fen\u002Fdocs\u002Fplugins\u002Fcalls\u002F\">the calls\u003C\u002Fa> give an example in TS and in Rust for each method.\u003C\u002Fp>\n\u003Ch2 id=\"when-the-program-starts\">When the program starts\u003C\u002Fh2>\n\u003Cul>\n\u003Cli>\u003Cstrong>At its first activation, never before.\u003C\u002Fstrong> Escouade starts the program of \u003Ccode class=\"key\">run\u003C\u002Fcode> when a fact of its \u003Ccode class=\"key\">activation\u003C\u002Fcode> list happens (\u003Ccode class=\"key\">event:ticket.column\u003C\u002Fcode>), or at the first call it makes to it. A plugin without \u003Ccode class=\"key\">activation\u003C\u002Fcode> does not run; a plugin without \u003Ccode class=\"key\">run\u003C\u002Fcode> is declarative: nothing runs.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>One process per plugin.\u003C\u002Fstrong> It serves every project and every agent where the plugin is enabled: Escouade tells its calls apart by their \u003Ccode class=\"key\">id\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>In the plugin’s folder\u003C\u002Fstrong>, with three variables: \u003Ccode class=\"key\">ESCOUADE_PLUGIN_ID\u003C\u002Fcode> (its id), \u003Ccode class=\"key\">ESCOUADE_PLUGIN_API\u003C\u002Fcode> (the protocol’s version, \u003Ccode class=\"key\">1\u003C\u002Fcode>) and \u003Ccode class=\"key\">ESCOUADE_VERSION\u003C\u002Fcode> (Escouade’s).\u003C\u002Fli>\n\u003Cli>\u003Cstrong>With everything it starts.\u003C\u002Fstrong> On Windows, the process is in a Job object; on macOS and Linux, it leads its process group. When Escouade stops it, what it started stops with it; on Windows, even if Escouade crashes.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"the-messages\">The messages\u003C\u002Fh2>\n\u003Cul>\n\u003Cli>JSON-RPC 2.0, \u003Cstrong>one message per line\u003C\u002Fstrong>: JSON in UTF-8, without a line break, ended by \u003Ccode class=\"key\">\\n\u003C\u002Fcode>. The standard output is for the protocol only; write your traces on the standard error, which goes to the \u003Ca href=\"#the-journal\">journal\u003C\u002Fa>.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Batches both ways\u003C\u002Fstrong>: a JSON array of messages, on one line. Answer a batch with a batch; the answers can come in any order, each with the \u003Ccode class=\"key\">id\u003C\u002Fcode> of its request.\u003C\u002Fli>\n\u003Cli>A notification has no \u003Ccode class=\"key\">id\u003C\u002Fcode> and expects no answer.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cpre>\u003Ccode class=\"language-json\">{&quot;jsonrpc&quot;:&quot;2.0&quot;,&quot;id&quot;:7,&quot;method&quot;:&quot;echo&quot;,&quot;params&quot;:{&quot;a&quot;:1}}\n[{&quot;jsonrpc&quot;:&quot;2.0&quot;,&quot;id&quot;:8,&quot;method&quot;:&quot;echo&quot;,&quot;params&quot;:1},{&quot;jsonrpc&quot;:&quot;2.0&quot;,&quot;method&quot;:&quot;log&quot;,&quot;params&quot;:{&quot;level&quot;:&quot;info&quot;,&quot;message&quot;:&quot;ready&quot;}}]\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"the-handshake\">The handshake\u003C\u002Fh2>\n\u003Cp>Escouade’s first message is \u003Ccode class=\"key\">initialize\u003C\u002Fcode>. It gives the version of the protocol it speaks and what it can do:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;jsonrpc&quot;: &quot;2.0&quot;,\n  &quot;id&quot;: 1,\n  &quot;method&quot;: &quot;initialize&quot;,\n  &quot;params&quot;: {\n    &quot;protocolVersion&quot;: 1,\n    &quot;escouade&quot;: { &quot;version&quot;: &quot;2.1.1&quot; },\n    &quot;plugin&quot;: { &quot;id&quot;: &quot;dev.escouade.slack&quot;, &quot;version&quot;: &quot;1.1.0&quot; },\n    &quot;lang&quot;: &quot;en&quot;,\n    &quot;capabilities&quot;: { &quot;batch&quot;: true, &quot;secrets&quot;: true, &quot;settings&quot;: true, &quot;log&quot;: true, &quot;attach&quot;: true }\n  }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>The plugin answers the version it speaks and its own capabilities:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{&quot;jsonrpc&quot;:&quot;2.0&quot;,&quot;id&quot;:1,&quot;result&quot;:{&quot;protocolVersion&quot;:1,&quot;capabilities&quot;:{&quot;batch&quot;:true}}}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cul>\n\u003Cli>\u003Ccode class=\"key\">lang\u003C\u002Fcode> is the language of the interface (\u003Ccode class=\"key\">fr\u003C\u002Fcode>, \u003Ccode class=\"key\">en\u003C\u002Fcode>), for what the plugin writes for the user.\u003C\u002Fli>\n\u003Cli>Another version, or an answer without a version, stops the plugin, with the reason: “it speaks version 2 of the protocol; this Escouade speaks version 1”.\u003C\u002Fli>\n\u003Cli>Without an answer within 10 seconds, the plugin is stopped too.\u003C\u002Fli>\n\u003Cli>Nothing else is sent to it before its answer: what comes meanwhile waits in a queue.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"the-delays\">The delays\u003C\u002Fh2>\n\u003Cp>Every call of Escouade has a delay. Past it, Escouade stops waiting: \u003Cstrong>its default answer applies\u003C\u002Fstrong> (a permission request goes to the user, as without a plugin), and a late answer is ignored.\u003C\u002Fp>\n\u003Cdiv class=\"table\">\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Method\u003C\u002Fth>\n\u003Cth>Delay\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\u003Ctr>\n\u003Ctd>\u003Ccode class=\"key\">initialize\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>10 s\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode class=\"key\">shutdown\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>1 s\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode class=\"key\">permissions\u002F…\u003C\u002Fcode> (a decision an agent waits for)\u003C\u002Ftd>\n\u003Ctd>500 ms\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode class=\"key\">tickets\u002F…\u003C\u002Fcode>, \u003Ccode class=\"key\">forge\u002F…\u003C\u002Fcode>, \u003Ccode class=\"key\">workflow\u002F…\u003C\u002Fcode>, \u003Ccode class=\"key\">engine\u002FoneShot\u003C\u002Fcode> (an import, a call to a service)\u003C\u002Ftd>\n\u003Ctd>60 s\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>The others\u003C\u002Ftd>\n\u003Ctd>5 s\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\u003C\u002Ftable>\u003C\u002Fdiv>\n\u003Cp>Every delay that runs out is noted in the journal: “test\u002Fdo: no answer within 300 ms”.\u003C\u002Fp>\n\u003Ch2 id=\"what-escouade-serves-the-plugin\">What Escouade serves the plugin\u003C\u002Fh2>\n\u003Cdiv class=\"table\">\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Method\u003C\u002Fth>\n\u003Cth>Parameters\u003C\u002Fth>\n\u003Cth>Answer\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\u003Ctr>\n\u003Ctd>\u003Ccode class=\"key\">secrets\u002Fget\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>{ &quot;key&quot;: &quot;webhook&quot; }\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The secret, or \u003Ccode class=\"key\">null\u003C\u002Fcode> if none is kept under that key.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode class=\"key\">settings\u002Fget\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode class=\"key\">{}\u003C\u002Fcode>, or \u003Ccode>{ &quot;project&quot;: &quot;&lt;id&gt;&quot; }\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Its settings, as the user chose them in the Settings tab of its page: an object by key of \u003Ccode class=\"key\">contributes.settings\u003C\u002Fcode>, without those left to their default. With a project (the \u003Ccode class=\"key\">project.id\u003C\u002Fcode> of a fact), those its \u003Ca href=\"\u002Fen\u002Fdocs\u002Fescouade-yml\u002F\">\u003Ccode class=\"key\">.escouade.yml\u003C\u002Fcode>\u003C\u002Fa> gives it under \u003Ccode class=\"key\">plugins.&lt;id&gt;\u003C\u002Fcode> go first, if it is on in that project: only the keys it declares, with a value of their type, never a secret.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode class=\"key\">log\u003C\u002Fcode> (notification)\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>{ &quot;level&quot;: &quot;info&quot;, &quot;message&quot;: &quot;…&quot; }\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>None: the line goes to the plugin’s journal.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode class=\"key\">files\u002Fread\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>{ &quot;path&quot;: &quot;data\u002Fwords.txt&quot; }\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode class=\"key\">&quot;…&quot;\u003C\u002Fcode>, the text of a file of its folder, as UTF-8 text, 256 KB at most, on both transports. Refused (\u003Ccode class=\"key\">-32003\u003C\u002Fcode>, kept in the journal): an absolute path, with \u003Ccode class=\"key\">..\u003C\u002Fcode> or a drive, a link that leads out of the folder, a hidden name (\u003Ccode class=\"key\">.env\u003C\u002Fcode>, \u003Ccode class=\"key\">.git\u003C\u002Fcode>), the manifest. \u003Ccode class=\"key\">-32005\u003C\u002Fcode> for a missing file, \u003Ccode class=\"key\">-32004\u003C\u002Fcode> for a file too big or that is not text.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode class=\"key\">net\u002Ffetch\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>{ &quot;url&quot;: &quot;https:\u002F\u002F…&quot;, &quot;method&quot;: &quot;POST&quot;, &quot;headers&quot;: {}, &quot;body&quot;: &quot;…&quot; }\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>{ &quot;status&quot;: 200, &quot;headers&quot;: {}, &quot;body&quot;: &quot;…&quot; }\u003C\u002Fcode>, for a plugin in WASM only: see \u003Ca href=\"#the-network-through-escouade\">the network\u003C\u002Fa>.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode class=\"key\">net\u002Fstream\u003C\u002Fcode>, \u003Ccode class=\"key\">ws\u002F*\u003C\u002Fcode>, \u003Ccode class=\"key\">timer\u002F*\u003C\u002Fcode>, \u003Ccode class=\"key\">fs\u002F*\u003C\u002Fcode>, \u003Ccode class=\"key\">proc\u002F*\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>See \u003Ca href=\"\u002Fen\u002Fdocs\u002Fplugins\u002Fcalls\u002F\">the calls\u003C\u002Fa>.\u003C\u002Ftd>\n\u003Ctd>For a plugin in WASM only; a process gets \u003Ccode class=\"key\">-32601\u003C\u002Fcode>.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\u003C\u002Ftable>\u003C\u002Fdiv>\n\u003Cp>Any other method answers the error \u003Ccode class=\"key\">-32601\u003C\u002Fcode> and notes it in the journal. Escouade’s own errors: \u003Ccode class=\"key\">-32001\u003C\u002Fcode> for a secret that is not the plugin’s, \u003Ccode class=\"key\">-32002\u003C\u002Fcode> for a keychain out of reach, \u003Ccode class=\"key\">-32003\u003C\u002Fcode> for what the plugin may not reach (a host, a file), \u003Ccode class=\"key\">-32004\u003C\u002Fcode> for a request that fails on the way, \u003Ccode class=\"key\">-32005\u003C\u002Fcode> for a missing file, \u003Ccode class=\"key\">-32602\u003C\u002Fcode> for parameters that do not hold.\u003C\u002Fp>\n\u003Ch2 id=\"the-secrets\">The secrets\u003C\u002Fh2>\n\u003Cp>A plugin’s secrets are in the system’s keychain, under a name of its own: \u003Ccode class=\"key\">plugin:&lt;id&gt;:&lt;key&gt;\u003C\u002Fcode>. It asks for them with \u003Ccode class=\"key\">secrets\u002Fget\u003C\u002Fcode>, when it needs them.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Only its own.\u003C\u002Fstrong> The key must be one of its \u003Ccode class=\"key\">permissions.secrets\u003C\u002Fcode>; a pattern ending with \u003Ccode class=\"key\">*\u003C\u002Fcode> takes in several (\u003Ccode class=\"key\">account:*\u003C\u002Fcode>). Any other key is refused with \u003Ccode class=\"key\">-32001\u003C\u002Fcode> and noted in the journal, even another plugin’s written in full: \u003Ccode>plugin:dev.other:token\u003C\u002Fcode> is not a key of its permissions.\u003C\u002Fli>\n\u003Cli>A plugin never gets the secret of an account, a harness or an integration of Escouade.\u003C\u002Fli>\n\u003Cli>A secret given to the plugin is masked (\u003Ccode class=\"key\">••••\u003C\u002Fcode>) everywhere in its journal, its standard error included.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"crashes-and-restarts\">Crashes and restarts\u003C\u002Fh2>\n\u003Cul>\n\u003Cli>\u003Cstrong>A crash restarts the plugin\u003C\u002Fstrong>, three times within five minutes. At the fourth, it is stopped: “stopped after 4 crashes within 5 minutes”. It no longer starts by itself; the “Restart” button of its journal, in the view Plugins, starts it again, its crashes forgotten.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>The calls under way\u003C\u002Fstrong> when it crashes fail: their default answer applies.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>While it restarts\u003C\u002Fstrong>, what Escouade sends it waits in a queue of 256 messages, then goes in order once \u003Ccode class=\"key\">initialize\u003C\u002Fcode> is answered. A full queue loses its oldest message, and the journal says so.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>What each agent had attached to it\u003C\u002Fstrong> (for a harness, \u003Ccode class=\"key\">agent\u002Fattach\u003C\u002Fcode>: its session, its mode, its model) is given again to the new process before any other message.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"the-memory\">The memory\u003C\u002Fh2>\n\u003Cp>Escouade reads the memory of the plugin and of everything it started every two seconds, as it does the agents’. Past 1 GB, the plugin is stopped, without a restart: “stopped: 1100 MB of memory, past the limit of 1024 MB”.\u003C\u002Fp>\n\u003Ch2 id=\"when-escouade-closes\">When Escouade closes\u003C\u002Fh2>\n\u003Cp>When Escouade closes, it sends \u003Ccode class=\"key\">shutdown\u003C\u002Fcode> to each running plugin, waits for its answer a second at most for all of them, then stops its process and everything it started. Answer \u003Ccode class=\"key\">shutdown\u003C\u002Fcode>, then exit: what you have not written before is lost.\u003C\u002Fp>\n\u003Ch2 id=\"the-journal\">The journal\u003C\u002Fh2>\n\u003Cp>Each plugin has its journal, in \u003Ccode>~\u002F.escouade\u002Fplugin-logs\u002F&lt;id&gt;.log\u003C\u002Fcode>; past 1 MB, it becomes \u003Ccode class=\"key\">&lt;id&gt;.log.1\u003C\u002Fcode> and starts over. Its lines are in English, like the app’s log, and note:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>its starts, its answer to \u003Ccode class=\"key\">initialize\u003C\u002Fcode>, its stops and their reason;\u003C\u002Fli>\n\u003Cli>its crashes, with their exit code and its last lines of standard error;\u003C\u002Fli>\n\u003Cli>each line of its standard error;\u003C\u002Fli>\n\u003Cli>its messages: its \u003Ccode class=\"key\">log\u003C\u002Fcode>s, the secrets it asked for (never their value);\u003C\u002Fli>\n\u003Cli>the calls left without an answer, what it asked for and was refused, the messages lost from a full queue.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>The last 500 lines are read in the Journal tab of its page, in the view Plugins (\u003Ca href=\"\u002Fen\u002Fdocs\u002Fplugins\u002Finstaller\u002F#the-journal\">installing a plugin\u003C\u002Fa>).\u003C\u002Fp>\n\u003Cp>Its methods, their parameters, their answers and their delays are described by a schema, \u003Ca href=\"\u002Fschemas\u002Fplugin-protocol-1.json\">\u003Ccode>plugin-protocol-1.json\u003C\u002Fcode>\u003C\u002Fa>, where the types of the \u003Ca href=\"\u002Fen\u002Fdocs\u002Fplugins\u002Fsdk\u002F\">TypeScript SDK\u003C\u002Fa> come from. To check that a plugin keeps to this protocol without starting the app, play \u003Ca href=\"\u002Fen\u002Fdocs\u002Fplugins\u002Ftesting\u002F\">the conformance bench\u003C\u002Fa>: \u003Ccode>escouade-plugin test &lt;folder&gt;\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Ch2 id=\"a-program-in-wasm\">A program in WASM\u003C\u002Fh2>\n\u003Cp>A plugin whose manifest says \u003Ccode>&quot;run&quot;: { &quot;js&quot;: &quot;dist\u002Fplugin.mjs&quot; }\u003C\u002Fcode> or \u003Ccode>&quot;run&quot;: { &quot;wasm&quot;: &quot;Cargo.toml&quot; }\u003C\u002Fcode> starts no process: Escouade runs its module \u003Cstrong>inside the app, in WASM\u003C\u002Fstrong>, with no Node on the machine. One instance per plugin, on a thread of its own, which keeps its state from one call to the next like a process. The protocol is the same: the same messages, carried by the module’s standard input and output, the same handshake, the same delays, the same journal and the same restarts; \u003Ca href=\"\u002Fen\u002Fdocs\u002Fplugins\u002Fsdk\u002F\">the SDK\u003C\u002Fa> and \u003Ca href=\"\u002Fen\u002Fdocs\u002Fplugins\u002Frust\u002F\">the Rust kit\u003C\u002Fa> speak it for you.\u003C\u002Fp>\n\u003Cp>The module is compiled once: by the store for a plugin of the store (the app downloads it and checks its fingerprint against \u003Ccode class=\"key\">version.json\u003C\u002Fcode>), by the app for a \u003Ccode class=\"key\">run.js\u003C\u002Fcode> plugin installed from a folder or a git repository. A \u003Ccode class=\"key\">run.wasm\u003C\u002Fcode> plugin outside the store comes with its \u003Ccode class=\"key\">plugin.wasm\u003C\u002Fcode>, beside the manifest. The app precompiles it at install, keeps it in its cache and precompiles it again after an update of Escouade. It needs Escouade 2.2.0 or later.\u003C\u002Fp>\n\u003Ch3 id=\"the-network-through-escouade\">The network, through Escouade\u003C\u002Fh3>\n\u003Cp>A plugin in WASM opens no connection: everything goes through Escouade, by \u003Ccode class=\"key\">net\u002Ffetch\u003C\u002Fcode>, \u003Ccode class=\"key\">net\u002Fstream\u003C\u002Fcode> and \u003Ccode class=\"key\">ws\u002Fopen\u003C\u002Fcode>, which only a plugin in WASM gets (a process has its own network, and these methods answer it \u003Ccode class=\"key\">-32601\u003C\u002Fcode>). \u003Ccode class=\"key\">fetch\u003C\u002Fcode> in JS and the kit’s client in Rust lead there. Escouade sends the request:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>over https only\u003C\u002Fstrong>, to a host of \u003Ccode class=\"key\">permissions.network\u003C\u002Fcode> or one of its subdomains (\u003Ccode class=\"key\">slack.com\u003C\u002Fcode> covers \u003Ccode class=\"key\">api.slack.com\u003C\u002Fcode>); never to an IP address written in the URL, which \u003Ccode class=\"key\">permissions.network\u003C\u002Fcode> does not name;\u003C\u002Fli>\n\u003Cli>\u003Cstrong>following no redirect\u003C\u002Fstrong>: the plugin gets the 3xx answer;\u003C\u002Fli>\n\u003Cli>\u003Cstrong>within 10 s, for an answer of 1 MiB at most\u003C\u002Fstrong>, unless \u003Ccode class=\"key\">limits\u003C\u002Fcode> asks for more;\u003C\u002Fli>\n\u003Cli>\u003Cstrong>never to an address of the machine or of the local network\u003C\u002Fstrong>, even for a declared host: loopback, private, link-local, unspecified, multicast or broadcast, in IPv4 as in IPv6. Escouade judges the addresses the connection itself uses, once the name is resolved, not a separate lookup the DNS could contradict;\u003C\u002Fli>\n\u003Cli>\u003Cstrong>through the proxy of Settings › Network\u003C\u002Fstrong>, if there is one: only the name is judged then, and the journal says so.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>\u003Ccode class=\"key\">{account}\u003C\u002Fcode>, the host of an account the user typed, and \u003Ccode class=\"key\">local:shared\u003C\u002Fcode>, a harness’s local server, open http and the local network for these hosts alone: see \u003Ca href=\"\u002Fen\u002Fdocs\u002Fplugins\u002Fcapabilities\u002F#the-network\">the host’s capabilities\u003C\u002Fa>.\u003C\u002Fp>\n\u003Cp>A refused call answers \u003Ccode class=\"key\">-32003\u003C\u002Fcode> and the journal keeps it (“slack.com resolves to 127.0.0.1, an address of this machine or of the local network”, “example.org is not in permissions.network of …”); a call that fails on the way (no answer in time, an answer too large, a host out of reach) answers \u003Ccode class=\"key\">-32004\u003C\u002Fcode>. The plugin sees a \u003Ccode class=\"key\">fetch\u003C\u002Fcode> that rejects.\u003C\u002Fp>\n\u003Ch3 id=\"the-files-of-its-folder\">The files of its folder\u003C\u002Fh3>\n\u003Cul>\n\u003Cli>\u003Cstrong>The JSON files at its root\u003C\u002Fstrong> (\u003Ccode class=\"key\">rules.json\u003C\u002Fcode>, \u003Ccode class=\"key\">channels.json\u003C\u002Fcode>) are given to it at its start: 256 KB each, 1 MB in all, never its manifest. \u003Ccode class=\"key\">readFileSync\u003C\u002Fcode> of \u003Ccode class=\"key\">node:fs\u003C\u002Fcode> reads them, as under Node (\u003Ccode>readFileSync(new URL('..\u002Frules.json', globalThis._importMeta_.url), 'utf8')\u003C\u002Fcode>).\u003C\u002Fli>\n\u003Cli>\u003Cstrong>The others\u003C\u002Fstrong> are read by \u003Ccode class=\"key\">files\u002Fread\u003C\u002Fcode> (the SDK’s \u003Ccode class=\"key\">p.file()\u003C\u002Fcode>), as UTF-8 text, 256 KB at most, a subfolder included (\u003Ccode class=\"key\">data\u002Fwords.txt\u003C\u002Fcode>).\u003C\u002Fli>\n\u003Cli>\u003Cstrong>The rest\u003C\u002Fstrong> goes through \u003Ccode class=\"key\">fs\u002F*\u003C\u002Fcode>, under the roots of \u003Ccode class=\"key\">permissions.files\u003C\u002Fcode>: see \u003Ca href=\"\u002Fen\u002Fdocs\u002Fplugins\u002Fcapabilities\u002F#the-files-files\">the files\u003C\u002Fa>.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3 id=\"the-limits\">The limits\u003C\u002Fh3>\n\u003Cul>\n\u003Cli>\u003Cstrong>64 MB of memory, 2 s per turn of work\u003C\u002Fstrong>, 500 ms while it holds a decision.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>An endless loop, a growing memory or an uncaught exception\u003C\u002Fstrong> end the instance, and the journal says why (“a turn ran past 2000 ms: Escouade stopped its instance”, “its instance crashed: …”); a new one is made, as after a process crashes, with the same three restarts in five minutes, then the plugin is stopped.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Timers\u003C\u002Fstrong>: \u003Ccode class=\"key\">setTimeout\u003C\u002Fcode> and \u003Ccode class=\"key\">clearTimeout\u003C\u002Fcode> exist in JS, \u003Ccode class=\"key\">sleep\u003C\u002Fcode> and \u003Ccode class=\"key\">timeout\u003C\u002Fcode> in the kit, 10 ms at least, 64 at once.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>A bundle made with SDK 0.2\u003C\u002Fstrong> does not run in WASM: the plugin stops and its journal says to rebuild it with \u003Ccode class=\"key\">@escouade-dev\u002Fplugin\u003C\u002Fcode> 0.3 or later.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3 id=\"what-a-plugin-in-wasm-cannot-do\">What a plugin in WASM cannot do\u003C\u002Fh3>\n\u003Cp>Nothing it does not declare: no network, no file outside its folder, no program. It does not read the environment and loads no native or remote module. A bundle that imports anything other than \u003Ccode class=\"key\">node:fs\u003C\u002Fcode> and \u003Ccode class=\"key\">node:readline\u003C\u002Fcode>, or that has \u003Ccode class=\"key\">export\u003C\u002Fcode>s, is not compiled, and the plugin stops with the reason. Its secrets and settings are its own only, as for a process. That is why the install window says “Runs inside Escouade, isolated”, then each \u003Ca href=\"\u002Fen\u002Fdocs\u002Fplugins\u002Fcapabilities\u002F\">capability\u003C\u002Fa> it asks for, not “Runs a program on your computer, with your rights”.\u003C\u002Fp>\n\u003Ch2 id=\"what-the-user-changes\">What the user changes\u003C\u002Fh2>\n\u003Cp>A plugin switched off, a permission taken back or given again, an update installed: its process is stopped, and the next one gets the manifest and the permissions of now. A secret’s permission taken back makes \u003Ccode class=\"key\">secrets\u002Fget\u003C\u002Fcode> answer \u003Ccode class=\"key\">-32001\u003C\u002Fcode> for that key, as for a secret that is not its own.\u003C\u002Fp>\n",[8,12,15,18,21,24,27,30,33,36,39,42,46,49,52,55],{"id":9,"text":10,"depth":11},"when-the-program-starts","When the program starts",2,{"id":13,"text":14,"depth":11},"the-messages","The messages",{"id":16,"text":17,"depth":11},"the-handshake","The handshake",{"id":19,"text":20,"depth":11},"the-delays","The delays",{"id":22,"text":23,"depth":11},"what-escouade-serves-the-plugin","What Escouade serves the plugin",{"id":25,"text":26,"depth":11},"the-secrets","The secrets",{"id":28,"text":29,"depth":11},"crashes-and-restarts","Crashes and restarts",{"id":31,"text":32,"depth":11},"the-memory","The memory",{"id":34,"text":35,"depth":11},"when-escouade-closes","When Escouade closes",{"id":37,"text":38,"depth":11},"the-journal","The journal",{"id":40,"text":41,"depth":11},"a-program-in-wasm","A program in WASM",{"id":43,"text":44,"depth":45},"the-network-through-escouade","The network, through Escouade",3,{"id":47,"text":48,"depth":45},"the-files-of-its-folder","The files of its folder",{"id":50,"text":51,"depth":45},"the-limits","The limits",{"id":53,"text":54,"depth":45},"what-a-plugin-in-wasm-cannot-do","What a plugin in WASM cannot do",{"id":56,"text":57,"depth":11},"what-the-user-changes","What the user changes",1793889382194]