@rubytech/create-maxy-code 0.1.360 → 0.1.362
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/payload/platform/plugins/admin/skills/connector-directory/SKILL.md +53 -0
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +18 -1
- package/payload/platform/plugins/docs/references/graph.md +17 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +18 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/webchat-channel/dist/instructions.d.ts.map +1 -1
- package/payload/platform/services/webchat-channel/dist/instructions.js +11 -0
- package/payload/platform/services/webchat-channel/dist/instructions.js.map +1 -1
- package/payload/server/public/assets/{AdminLoginScreens-FufN0KIm.js → AdminLoginScreens-D6JtrY9a.js} +1 -1
- package/payload/server/public/assets/{AdminShell-CmFCbj38.js → AdminShell-CaVRZTmj.js} +1 -1
- package/payload/server/public/assets/{Checkbox-njajXf-b.js → Checkbox-BiHO47mz.js} +1 -1
- package/payload/server/public/assets/{OperatorConversations-B9Vx84BF.js → OperatorConversations-CzhbxFwN.js} +1 -1
- package/payload/server/public/assets/OperatorConversations-D3UBU6SM.css +1 -0
- package/payload/server/public/assets/{admin-DYOYaRcc.js → admin-BrCL9qSn.js} +1 -1
- package/payload/server/public/assets/{browser-CUwGN38H.js → browser-DNIV6o_p.js} +1 -1
- package/payload/server/public/assets/{calendar-HonT_eBL.js → calendar-Arx94jTH.js} +1 -1
- package/payload/server/public/assets/chat-BwShkhcz.js +1 -0
- package/payload/server/public/assets/{data-vEYM-aPa.js → data-DBInTT-w.js} +1 -1
- package/payload/server/public/assets/{graph-labels-tUzvK8mv.js → graph-labels-CVZClzPb.js} +1 -1
- package/payload/server/public/assets/{graph-BLUtXXda.js → graph-z-RYpYMZ.js} +15 -15
- package/payload/server/public/assets/{operator-BLEBOkrQ.js → operator-CoCf8Lk7.js} +1 -1
- package/payload/server/public/assets/{page-CcnJY-yB.js → page-CWnkJCxo.js} +5 -5
- package/payload/server/public/assets/{public-B8GW-fZg.js → public-CKMOOQiq.js} +1 -1
- package/payload/server/public/assets/{rotate-ccw-BA0MN5YR.js → rotate-ccw-Dgw6hmsm.js} +1 -1
- package/payload/server/public/browser.html +4 -4
- package/payload/server/public/calendar.html +4 -4
- package/payload/server/public/chat.html +6 -6
- package/payload/server/public/data.html +4 -4
- package/payload/server/public/graph.html +7 -7
- package/payload/server/public/index.html +6 -6
- package/payload/server/public/operator.html +8 -8
- package/payload/server/public/public.html +6 -6
- package/payload/server/server.js +106 -0
- package/payload/server/public/assets/OperatorConversations-BqudLZH5.css +0 -1
- package/payload/server/public/assets/chat-B-sCk0kO.js +0 -1
package/package.json
CHANGED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: connector-directory
|
|
3
|
+
description: Use when the operator asks to use, connect, install, or integrate with a service you are not already connected to, such as "can you use the Stripe connector", "connect my Notion", "install the official Linear plugin", "do you integrate with Figma". This is how you discover a claude.ai directory connector that is not yet loaded for this client, before you deny it or fall back to the generic static-key connector. It searches Anthropic's public connector catalogue by keyword, hands the operator the claude.ai link to enable the one they want, then confirms the connection inherited and proves it with one live read. Discovery is catalogue-driven: any connector in the directory is findable with no per-connector setup here. Not for a connector that already shows as loaded (use it directly) or one that shows as needing authentication (that is the connector-authenticate path, not discovery).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Connector directory
|
|
7
|
+
|
|
8
|
+
Invoked by the admin agent directly.
|
|
9
|
+
|
|
10
|
+
claude.ai offers a directory of managed connectors like Stripe, Notion, Linear, Figma, Cloudflare and hundreds more. A connector the operator has already enabled on claude.ai shows up here on its own and you use it directly. This skill is for the other case: the operator asks about a connector that is **not** loaded for this client yet, and you need to find out whether it exists and route the operator to enable it.
|
|
11
|
+
|
|
12
|
+
Without this skill you can only act on connectors already connected, so you would either deny a real integration or fall back to the generic static-key connector. The connector directory is the catalogue that closes that gap.
|
|
13
|
+
|
|
14
|
+
## When this applies
|
|
15
|
+
|
|
16
|
+
The operator asks to use, connect, or install an integration, and `capabilities-here` (or `claude mcp list`) does **not** already list it. Before you tell the operator there is no such integration, and before you reach for the generic static-key connector, search the directory.
|
|
17
|
+
|
|
18
|
+
## Discover it
|
|
19
|
+
|
|
20
|
+
The catalogue is a public endpoint. Search it by the operator's keyword:
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
curl -s "https://api.anthropic.com/mcp-registry/v0/servers?version=latest&limit=100&visibility=commercial,gsuite,enterprise,health&search=stripe" \
|
|
24
|
+
| python3 -c 'import sys,json
|
|
25
|
+
for e in json.load(sys.stdin).get("servers",[]):
|
|
26
|
+
s=e.get("server",{}); m=e.get("_meta",{}).get("com.anthropic.api/mcp-registry",{})
|
|
27
|
+
print(m.get("displayName") or s.get("title"), "|", m.get("oneLiner"))
|
|
28
|
+
print(" enable:", m.get("directoryUrl")); print(" server:", m.get("url"), "| authless:", m.get("isAuthless"), "| tools:", len(m.get("toolNames",[])))'
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Swap `search=stripe` for the connector the operator named. The fields that matter per match are the display name, the one-line description, the `directoryUrl` (the claude.ai link that enables this exact connector), the server `url`, and the tool count. Running this is also the record of the discovery in the session log, so a later "you missed a connector I asked for" is diagnosable from the transcript.
|
|
32
|
+
|
|
33
|
+
If the search returns nothing, the connector is not in the directory. Say so plainly and offer the generic static-key connector instead. Do not invent a directory entry or a connector that the search did not return.
|
|
34
|
+
|
|
35
|
+
If the endpoint itself does not answer, do not deny the request on that basis: point the operator at the directory at `https://claude.ai/code#settings/customize-connectors` and ask them to browse for the connector there.
|
|
36
|
+
|
|
37
|
+
## Route the enable step to claude.ai
|
|
38
|
+
|
|
39
|
+
You enable nothing from here. Enabling a directory connector happens on claude.ai, by the operator. Send them the `directoryUrl` from the match and ask them to open it, enable the connector, and complete its sign-in. If a match has no `directoryUrl`, send them to `https://claude.ai/code#settings/customize-connectors` and tell them to choose **Browse connectors** and find it by name. Do not try to render an in-chat connect button; that affordance does not exist here, the claude.ai link is the enable path.
|
|
40
|
+
|
|
41
|
+
## Confirm it inherited
|
|
42
|
+
|
|
43
|
+
A connector enabled on claude.ai propagates to this client on its own; there is no step to trigger here. Once the operator says they have enabled it, confirm this client picked it up:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
claude mcp list
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The connector reads as **Connected** rather than needing authentication. If it still shows as needing authentication, the claude.ai side is not finished or this client needs the one-off per-client authentication, which is the connector-authenticate path, not a directory problem.
|
|
50
|
+
|
|
51
|
+
## Prove it
|
|
52
|
+
|
|
53
|
+
Make one live read against the connector and confirm a result comes back: list something, fetch one record, or read one item the connector grants. A read that returns a result is the proof the connector works end to end for this client. A read that fails while `claude mcp list` shows it Connected points at the connector's granted scope; report what the read returned rather than assuming a cause.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: platform-architecture
|
|
3
3
|
description: Use when grounding any documented-surface claim about what Maxy ships — plugins, skills, specialists, install/deploy flows, internals. This is the install catalogue, not evidence of what is enabled on the current account. For install state on this account, call `capabilities-here`; for documented surface, cite the `Source:` URL inline.
|
|
4
|
-
content-hash: sha256:
|
|
4
|
+
content-hash: sha256:aa31039d259f4517fbd2bcda93ca7c32c55aa794ba124b8620df32597ffa4ad3
|
|
5
5
|
brand: maxy-code
|
|
6
6
|
product-name: Maxy
|
|
7
7
|
---
|
|
@@ -2891,6 +2891,23 @@ The **Graph** admin page (`/graph`) renders a force-directed view of your
|
|
|
2891
2891
|
account's Neo4j subgraph. Labels on the canvas follow the zoom level, so you
|
|
2892
2892
|
see the most useful identity at every scale.
|
|
2893
2893
|
|
|
2894
|
+
## Default view
|
|
2895
|
+
|
|
2896
|
+
Opening `/graph` with no filter chips selected shows **all your top-level
|
|
2897
|
+
nodes** straight away, so the canvas is populated on arrival rather than
|
|
2898
|
+
blank. Top-level nodes are the operator-meaningful entry points (People,
|
|
2899
|
+
Organizations, Conversations, Tasks, Jobs, and the rest of the filter chip
|
|
2900
|
+
roster); their child and infrastructural nodes stay off this view and are
|
|
2901
|
+
reached by clicking into a node's neighbourhood.
|
|
2902
|
+
|
|
2903
|
+
This view is capped at **1000 nodes**. When your graph has more than that, the
|
|
2904
|
+
canvas shows 1000 of them and a note reads, for example, `Showing 1,000 of
|
|
2905
|
+
3,412 top-level nodes`, so you know some are not on screen. When the count is
|
|
2906
|
+
under the cap the note reads `Showing 312 of 312 top-level nodes`. Selecting
|
|
2907
|
+
one or more filter chips switches to the chip-filtered view, which returns
|
|
2908
|
+
every matching node and asks you to narrow if a single label set exceeds its
|
|
2909
|
+
own larger limit.
|
|
2910
|
+
|
|
2894
2911
|
## Search and pivot
|
|
2895
2912
|
|
|
2896
2913
|
Type a term in the search box to highlight matching nodes on the canvas. Hits get an amber border so you can pick them out of a busy view. Click any highlighted node to open its side panel and pivot into its neighbourhood — both clicks (hit and non-hit) behave identically.
|
|
@@ -4,6 +4,23 @@ The **Graph** admin page (`/graph`) renders a force-directed view of your
|
|
|
4
4
|
account's Neo4j subgraph. Labels on the canvas follow the zoom level, so you
|
|
5
5
|
see the most useful identity at every scale.
|
|
6
6
|
|
|
7
|
+
## Default view
|
|
8
|
+
|
|
9
|
+
Opening `/graph` with no filter chips selected shows **all your top-level
|
|
10
|
+
nodes** straight away, so the canvas is populated on arrival rather than
|
|
11
|
+
blank. Top-level nodes are the operator-meaningful entry points (People,
|
|
12
|
+
Organizations, Conversations, Tasks, Jobs, and the rest of the filter chip
|
|
13
|
+
roster); their child and infrastructural nodes stay off this view and are
|
|
14
|
+
reached by clicking into a node's neighbourhood.
|
|
15
|
+
|
|
16
|
+
This view is capped at **1000 nodes**. When your graph has more than that, the
|
|
17
|
+
canvas shows 1000 of them and a note reads, for example, `Showing 1,000 of
|
|
18
|
+
3,412 top-level nodes`, so you know some are not on screen. When the count is
|
|
19
|
+
under the cap the note reads `Showing 312 of 312 top-level nodes`. Selecting
|
|
20
|
+
one or more filter chips switches to the chip-filtered view, which returns
|
|
21
|
+
every matching node and asks you to narrow if a single label set exceeds its
|
|
22
|
+
own larger limit.
|
|
23
|
+
|
|
7
24
|
## Search and pivot
|
|
8
25
|
|
|
9
26
|
Type a term in the search box to highlight matching nodes on the canvas. Hits get an amber border so you can pick them out of a busy view. Click any highlighted node to open its side panel and pivot into its neighbourhood — both clicks (hit and non-hit) behave identically.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAe3B,OAAO,EAkBL,KAAK,SAAS,EAEf,MAAM,kBAAkB,CAAA;AAsBzB,OAAO,KAAK,EAAE,SAAS,EAAyB,MAAM,iBAAiB,CAAA;AAEvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,EAAqB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAA;AAgFlE,eAAO,MAAM,kBAAkB,QAA2B,CAAA;AAS1D,eAAO,MAAM,4BAA4B,QAAS,CAAA;AAIlD,MAAM,WAAW,QAAS,SAAQ,IAAI,CAAC,SAAS,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAC7E;;qEAEiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,SAAS,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IACnB,iBAAiB,EAAE,MAAM,CAAA;IACzB,kBAAkB,EAAE,WAAW,CAAA;IAC/B,eAAe,EAAE,aAAa,CAAA;IAC9B;kFAC8E;IAC9E,cAAc,EAAE,cAAc,CAAA;IAC9B;;gFAE4E;IAC5E,cAAc,EAAE,cAAc,CAAA;IAC9B;;;6BAGyB;IACzB,mBAAmB,CAAC,EAAE,mBAAmB,CAAA;IACzC;;;sCAGkC;IAClC,oBAAoB,CAAC,EAAE,oBAAoB,CAAA;IAC3C;;gFAE4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAsFD;;;;;;mEAMmE;AACnE,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAiC9D;AAMD;;;;;;iBAMiB;AACjB,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAmCvF;AAID;;;;;;yBAMyB;AACzB,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAiCzD;AAkCD,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAEpG;AAgBD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE1D;AAUD,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAMvE;AAMD,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,GACtB,MAAM,CAER;AAkCD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAe5F;AAwJD;;;kEAGkE;AAClE,MAAM,MAAM,OAAO,GAAG,IAAI,GAAG;IAC3B,2BAA2B,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACpD;;;+CAG2C;IAC3C,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CAChD,CAAA;AA8CD,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,
|
|
1
|
+
{"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAe3B,OAAO,EAkBL,KAAK,SAAS,EAEf,MAAM,kBAAkB,CAAA;AAsBzB,OAAO,KAAK,EAAE,SAAS,EAAyB,MAAM,iBAAiB,CAAA;AAEvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,EAAqB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAA;AAgFlE,eAAO,MAAM,kBAAkB,QAA2B,CAAA;AAS1D,eAAO,MAAM,4BAA4B,QAAS,CAAA;AAIlD,MAAM,WAAW,QAAS,SAAQ,IAAI,CAAC,SAAS,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAC7E;;qEAEiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,SAAS,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IACnB,iBAAiB,EAAE,MAAM,CAAA;IACzB,kBAAkB,EAAE,WAAW,CAAA;IAC/B,eAAe,EAAE,aAAa,CAAA;IAC9B;kFAC8E;IAC9E,cAAc,EAAE,cAAc,CAAA;IAC9B;;gFAE4E;IAC5E,cAAc,EAAE,cAAc,CAAA;IAC9B;;;6BAGyB;IACzB,mBAAmB,CAAC,EAAE,mBAAmB,CAAA;IACzC;;;sCAGkC;IAClC,oBAAoB,CAAC,EAAE,oBAAoB,CAAA;IAC3C;;gFAE4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAsFD;;;;;;mEAMmE;AACnE,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAiC9D;AAMD;;;;;;iBAMiB;AACjB,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAmCvF;AAID;;;;;;yBAMyB;AACzB,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAiCzD;AAkCD,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAEpG;AAgBD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE1D;AAUD,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAMvE;AAMD,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,GACtB,MAAM,CAER;AAkCD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAe5F;AAwJD;;;kEAGkE;AAClE,MAAM,MAAM,OAAO,GAAG,IAAI,GAAG;IAC3B,2BAA2B,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACpD;;;+CAG2C;IAC3C,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CAChD,CAAA;AA8CD,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CA2lFpD"}
|
|
@@ -1780,14 +1780,32 @@ export function buildHttpApp(deps) {
|
|
|
1780
1780
|
// window. This is the gate the webchat send path waits on before delivering
|
|
1781
1781
|
// a mid-turn message, so it is awaited (not fire-and-forget).
|
|
1782
1782
|
const stopMs = finitePositive(process.env.MAXY_INTERRUPT_STOP_MS, 2500);
|
|
1783
|
+
// Task 1205 — ESC is a no-op while the turn is blocked in a tool/subagent
|
|
1784
|
+
// call; re-send it every sub-window so it lands once the tool returns and the
|
|
1785
|
+
// turn re-enters token-streaming. The total window is unchanged.
|
|
1786
|
+
const reescalateMs = finitePositive(process.env.MAXY_INTERRUPT_REESCALATE_MS, 1000);
|
|
1783
1787
|
const sentAt = Date.now();
|
|
1784
1788
|
let stopped = false;
|
|
1789
|
+
let lastSendAt = sentAt;
|
|
1790
|
+
let escSends = 1; // the initial ESC already written above
|
|
1791
|
+
let canResend = true;
|
|
1785
1792
|
while (Date.now() - sentAt < stopMs) {
|
|
1786
1793
|
const st = deps.watcher.getBySessionId(sessionId)?.status;
|
|
1787
1794
|
if (st !== 'busy') {
|
|
1788
1795
|
stopped = true;
|
|
1789
1796
|
break;
|
|
1790
1797
|
}
|
|
1798
|
+
if (canResend && Date.now() - lastSendAt >= reescalateMs) {
|
|
1799
|
+
const re = writeInterruptToPty(sessionId, byte, deps.logger);
|
|
1800
|
+
lastSendAt = Date.now();
|
|
1801
|
+
if (re.ok) {
|
|
1802
|
+
escSends += 1;
|
|
1803
|
+
deps.logger(`[webchat-interrupt] op=interrupt-reescalated intId=${intId} sends=${escSends}`);
|
|
1804
|
+
}
|
|
1805
|
+
else {
|
|
1806
|
+
canResend = false; // child vanished mid-interrupt; the next status read exits the loop
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1791
1809
|
await new Promise((r) => setTimeout(r, 120));
|
|
1792
1810
|
}
|
|
1793
1811
|
const stoppedMs = stopped ? Date.now() - sentAt : null;
|