tandem-editor 0.11.2 → 0.12.0
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/.claude-plugin/plugin.json +2 -2
- package/CHANGELOG.md +135 -65
- package/README.md +9 -9
- package/dist/channel/index.js +59 -18
- package/dist/channel/index.js.map +1 -1
- package/dist/cli/index.js +102 -31
- package/dist/cli/index.js.map +1 -1
- package/dist/client/assets/{CoworkSettings-DK3jjdwK.js → CoworkSettings-C0cS9R7L.js} +1 -1
- package/dist/client/assets/event-CNdo2oXa.js +1 -0
- package/dist/client/assets/index-Dn5JwXA3.css +1 -0
- package/dist/client/assets/index-n-vFW5By.js +299 -0
- package/dist/client/assets/webview-Bhf-n_os.js +1 -0
- package/dist/client/assets/window-DePn7tLG.js +1 -0
- package/dist/client/index.html +4 -2
- package/dist/monitor/index.js +23 -16
- package/dist/monitor/index.js.map +1 -1
- package/dist/server/index.js +388 -331
- package/dist/server/index.js.map +1 -1
- package/package.json +7 -3
- package/dist/client/assets/index-CfT503n4.js +0 -297
- package/dist/client/assets/index-DeJe09pn.css +0 -1
- package/dist/client/assets/webview-Ben21ZLJ.js +0 -1
- package/dist/client/assets/window-BxBvHL5k.js +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -356,7 +356,7 @@ var init_uninstall_scrub = __esm({
|
|
|
356
356
|
});
|
|
357
357
|
|
|
358
358
|
// src/shared/constants.ts
|
|
359
|
-
var DEFAULT_MCP_PORT, TANDEM_REPO_URL, TANDEM_ISSUES_NEW_URL, MAX_FILE_SIZE,
|
|
359
|
+
var DEFAULT_MCP_PORT, TANDEM_REPO_URL, TANDEM_ISSUES_NEW_URL, MAX_FILE_SIZE, SESSION_MAX_AGE, CHANNEL_MAX_RETRIES, CHANNEL_RETRY_DELAY_MS, CHANNEL_CONNECT_FETCH_TIMEOUT_MS, CHANNEL_SSE_INACTIVITY_TIMEOUT_MS, CHANNEL_MODE_FETCH_TIMEOUT_MS, CHANNEL_AWARENESS_FETCH_TIMEOUT_MS, CHANNEL_ERROR_REPORT_TIMEOUT_MS, CHANNEL_REPLY_FETCH_TIMEOUT_MS, CHANNEL_PERMISSION_FETCH_TIMEOUT_MS, CHANNEL_MAX_SSE_BUFFER_BYTES, TOKEN_FILE_NAME;
|
|
360
360
|
var init_constants = __esm({
|
|
361
361
|
"src/shared/constants.ts"() {
|
|
362
362
|
"use strict";
|
|
@@ -364,8 +364,6 @@ var init_constants = __esm({
|
|
|
364
364
|
TANDEM_REPO_URL = "https://github.com/bloknayrb/tandem";
|
|
365
365
|
TANDEM_ISSUES_NEW_URL = `${TANDEM_REPO_URL}/issues/new`;
|
|
366
366
|
MAX_FILE_SIZE = 50 * 1024 * 1024;
|
|
367
|
-
MAX_WS_PAYLOAD = 10 * 1024 * 1024;
|
|
368
|
-
IDLE_TIMEOUT = 30 * 60 * 1e3;
|
|
369
367
|
SESSION_MAX_AGE = 30 * 24 * 60 * 60 * 1e3;
|
|
370
368
|
CHANNEL_MAX_RETRIES = 5;
|
|
371
369
|
CHANNEL_RETRY_DELAY_MS = 2e3;
|
|
@@ -667,7 +665,7 @@ var init_setup = __esm({
|
|
|
667
665
|
__dirname2 = dirname2(fileURLToPath2(import.meta.url));
|
|
668
666
|
PACKAGE_ROOT = resolve2(__dirname2, "../..");
|
|
669
667
|
CHANNEL_DIST = resolve2(PACKAGE_ROOT, "dist/channel/index.js");
|
|
670
|
-
MCP_URL = `http://
|
|
668
|
+
MCP_URL = `http://127.0.0.1:${DEFAULT_MCP_PORT}`;
|
|
671
669
|
}
|
|
672
670
|
});
|
|
673
671
|
|
|
@@ -689,7 +687,7 @@ function resolveTandemUrlCandidate(override) {
|
|
|
689
687
|
for (const url of candidates) {
|
|
690
688
|
if (url !== void 0 && url.trim() !== "") return url.trim();
|
|
691
689
|
}
|
|
692
|
-
return `http://
|
|
690
|
+
return `http://127.0.0.1:${DEFAULT_MCP_PORT}`;
|
|
693
691
|
}
|
|
694
692
|
function resolveAuthTokenCandidate(override) {
|
|
695
693
|
const candidates = [
|
|
@@ -1057,6 +1055,21 @@ ${err.stack ?? ""}
|
|
|
1057
1055
|
}
|
|
1058
1056
|
});
|
|
1059
1057
|
|
|
1058
|
+
// src/shared/api-paths.ts
|
|
1059
|
+
var API_EVENTS, API_CHANNEL_AWARENESS, API_CHANNEL_ERROR, API_CHANNEL_REPLY, API_CHANNEL_PERMISSION, API_MODE, API_ROTATE_TOKEN;
|
|
1060
|
+
var init_api_paths = __esm({
|
|
1061
|
+
"src/shared/api-paths.ts"() {
|
|
1062
|
+
"use strict";
|
|
1063
|
+
API_EVENTS = "/api/events";
|
|
1064
|
+
API_CHANNEL_AWARENESS = "/api/channel-awareness";
|
|
1065
|
+
API_CHANNEL_ERROR = "/api/channel-error";
|
|
1066
|
+
API_CHANNEL_REPLY = "/api/channel-reply";
|
|
1067
|
+
API_CHANNEL_PERMISSION = "/api/channel-permission";
|
|
1068
|
+
API_MODE = "/api/mode";
|
|
1069
|
+
API_ROTATE_TOKEN = "/api/rotate-token";
|
|
1070
|
+
}
|
|
1071
|
+
});
|
|
1072
|
+
|
|
1060
1073
|
// src/shared/fetch-with-timeout.ts
|
|
1061
1074
|
async function fetchWithTimeout(url, init, timeoutMs) {
|
|
1062
1075
|
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
@@ -1199,6 +1212,48 @@ var init_types = __esm({
|
|
|
1199
1212
|
}
|
|
1200
1213
|
});
|
|
1201
1214
|
|
|
1215
|
+
// src/shared/positions/types.ts
|
|
1216
|
+
var init_types2 = __esm({
|
|
1217
|
+
"src/shared/positions/types.ts"() {
|
|
1218
|
+
"use strict";
|
|
1219
|
+
}
|
|
1220
|
+
});
|
|
1221
|
+
|
|
1222
|
+
// src/shared/types.ts
|
|
1223
|
+
import { z } from "zod";
|
|
1224
|
+
var AnnotationTypeSchema, AnnotationStatusSchema, HighlightColorSchema, SeveritySchema, TandemModeSchema, AuthorSchema, ReplyAuthorSchema, AnnotationActionSchema, ExportFormatSchema, DocumentFormatSchema, ToolErrorCodeSchema, ChannelErrorCodeSchema, CHANNEL_CONNECT_FAILED;
|
|
1225
|
+
var init_types3 = __esm({
|
|
1226
|
+
"src/shared/types.ts"() {
|
|
1227
|
+
"use strict";
|
|
1228
|
+
init_types2();
|
|
1229
|
+
AnnotationTypeSchema = z.enum(["highlight", "note", "comment"]);
|
|
1230
|
+
AnnotationStatusSchema = z.enum(["pending", "accepted", "dismissed"]);
|
|
1231
|
+
HighlightColorSchema = z.enum(["yellow", "green", "blue", "pink"]);
|
|
1232
|
+
SeveritySchema = z.enum(["info", "warning", "error", "success"]);
|
|
1233
|
+
TandemModeSchema = z.enum(["solo", "tandem"]);
|
|
1234
|
+
AuthorSchema = z.enum(["user", "claude", "import"]);
|
|
1235
|
+
ReplyAuthorSchema = z.enum(["user", "claude"]);
|
|
1236
|
+
AnnotationActionSchema = z.enum(["accept", "dismiss"]);
|
|
1237
|
+
ExportFormatSchema = z.enum(["markdown", "json"]);
|
|
1238
|
+
DocumentFormatSchema = z.enum(["md", "txt", "html", "docx"]);
|
|
1239
|
+
ToolErrorCodeSchema = z.enum([
|
|
1240
|
+
"RANGE_GONE",
|
|
1241
|
+
"RANGE_MOVED",
|
|
1242
|
+
"FILE_LOCKED",
|
|
1243
|
+
"FILE_NOT_FOUND",
|
|
1244
|
+
"NO_DOCUMENT",
|
|
1245
|
+
"INVALID_RANGE",
|
|
1246
|
+
"INVALID_ARGUMENT",
|
|
1247
|
+
"NOT_FOUND",
|
|
1248
|
+
"ANNOTATION_RESOLVED",
|
|
1249
|
+
"FORMAT_ERROR",
|
|
1250
|
+
"PERMISSION_DENIED"
|
|
1251
|
+
]);
|
|
1252
|
+
ChannelErrorCodeSchema = z.enum(["CHANNEL_CONNECT_FAILED", "MONITOR_CONNECT_FAILED"]);
|
|
1253
|
+
CHANNEL_CONNECT_FAILED = "CHANNEL_CONNECT_FAILED";
|
|
1254
|
+
}
|
|
1255
|
+
});
|
|
1256
|
+
|
|
1202
1257
|
// src/channel/event-bridge.ts
|
|
1203
1258
|
async function startEventBridge(mcp, tandemUrl) {
|
|
1204
1259
|
let retries = 0;
|
|
@@ -1219,12 +1274,12 @@ async function startEventBridge(mcp, tandemUrl) {
|
|
|
1219
1274
|
console.error("[Channel] SSE connection exhausted, reporting error and exiting");
|
|
1220
1275
|
try {
|
|
1221
1276
|
await fetchWithTimeout(
|
|
1222
|
-
`${tandemUrl}
|
|
1277
|
+
`${tandemUrl}${API_CHANNEL_ERROR}`,
|
|
1223
1278
|
{
|
|
1224
1279
|
method: "POST",
|
|
1225
1280
|
headers: { "Content-Type": "application/json" },
|
|
1226
1281
|
body: JSON.stringify({
|
|
1227
|
-
error:
|
|
1282
|
+
error: CHANNEL_CONNECT_FAILED,
|
|
1228
1283
|
message: `Channel shim lost connection after ${CHANNEL_MAX_RETRIES} retries.`
|
|
1229
1284
|
})
|
|
1230
1285
|
},
|
|
@@ -1233,7 +1288,7 @@ async function startEventBridge(mcp, tandemUrl) {
|
|
|
1233
1288
|
} catch (reportErr) {
|
|
1234
1289
|
console.error(
|
|
1235
1290
|
"[Channel] Could not report failure to server:",
|
|
1236
|
-
describeFetchError(reportErr,
|
|
1291
|
+
describeFetchError(reportErr, API_CHANNEL_ERROR, CHANNEL_ERROR_REPORT_TIMEOUT_MS)
|
|
1237
1292
|
);
|
|
1238
1293
|
}
|
|
1239
1294
|
process.exit(1);
|
|
@@ -1252,7 +1307,7 @@ async function connectAndStream(mcp, tandemUrl, lastEventId, onEventId) {
|
|
|
1252
1307
|
);
|
|
1253
1308
|
let res;
|
|
1254
1309
|
try {
|
|
1255
|
-
res = await authFetch(`${tandemUrl}
|
|
1310
|
+
res = await authFetch(`${tandemUrl}${API_EVENTS}`, { headers, signal: connectCtrl.signal });
|
|
1256
1311
|
} finally {
|
|
1257
1312
|
clearTimeout(connectTimer);
|
|
1258
1313
|
}
|
|
@@ -1276,7 +1331,7 @@ async function connectAndStream(mcp, tandemUrl, lastEventId, onEventId) {
|
|
|
1276
1331
|
const AWARENESS_CLEAR_MS = 3e3;
|
|
1277
1332
|
function clearAwareness(documentId) {
|
|
1278
1333
|
fetchWithTimeout(
|
|
1279
|
-
`${tandemUrl}
|
|
1334
|
+
`${tandemUrl}${API_CHANNEL_AWARENESS}`,
|
|
1280
1335
|
{
|
|
1281
1336
|
method: "POST",
|
|
1282
1337
|
headers: { "Content-Type": "application/json" },
|
|
@@ -1290,7 +1345,11 @@ async function connectAndStream(mcp, tandemUrl, lastEventId, onEventId) {
|
|
|
1290
1345
|
).catch((err) => {
|
|
1291
1346
|
console.error(
|
|
1292
1347
|
"[Channel] clearAwareness failed (non-fatal):",
|
|
1293
|
-
describeFetchError(
|
|
1348
|
+
describeFetchError(
|
|
1349
|
+
err,
|
|
1350
|
+
`${API_CHANNEL_AWARENESS} clear`,
|
|
1351
|
+
CHANNEL_AWARENESS_FETCH_TIMEOUT_MS
|
|
1352
|
+
)
|
|
1294
1353
|
);
|
|
1295
1354
|
});
|
|
1296
1355
|
}
|
|
@@ -1299,7 +1358,7 @@ async function connectAndStream(mcp, tandemUrl, lastEventId, onEventId) {
|
|
|
1299
1358
|
const event = pendingAwareness;
|
|
1300
1359
|
pendingAwareness = null;
|
|
1301
1360
|
fetchWithTimeout(
|
|
1302
|
-
`${tandemUrl}
|
|
1361
|
+
`${tandemUrl}${API_CHANNEL_AWARENESS}`,
|
|
1303
1362
|
{
|
|
1304
1363
|
method: "POST",
|
|
1305
1364
|
headers: { "Content-Type": "application/json" },
|
|
@@ -1315,7 +1374,7 @@ async function connectAndStream(mcp, tandemUrl, lastEventId, onEventId) {
|
|
|
1315
1374
|
"[Channel] Awareness update failed:",
|
|
1316
1375
|
describeFetchError(
|
|
1317
1376
|
err,
|
|
1318
|
-
|
|
1377
|
+
`${API_CHANNEL_AWARENESS} update`,
|
|
1319
1378
|
CHANNEL_AWARENESS_FETCH_TIMEOUT_MS
|
|
1320
1379
|
)
|
|
1321
1380
|
);
|
|
@@ -1409,7 +1468,11 @@ async function getCachedMode(tandemUrl) {
|
|
|
1409
1468
|
const now = Date.now();
|
|
1410
1469
|
if (now - cachedModeAt < MODE_CACHE_TTL_MS) return cachedMode;
|
|
1411
1470
|
try {
|
|
1412
|
-
const res = await fetchWithTimeout(
|
|
1471
|
+
const res = await fetchWithTimeout(
|
|
1472
|
+
`${tandemUrl}${API_MODE}`,
|
|
1473
|
+
{},
|
|
1474
|
+
CHANNEL_MODE_FETCH_TIMEOUT_MS
|
|
1475
|
+
);
|
|
1413
1476
|
if (res.ok) {
|
|
1414
1477
|
const { mode } = await res.json();
|
|
1415
1478
|
cachedMode = mode;
|
|
@@ -1420,7 +1483,7 @@ async function getCachedMode(tandemUrl) {
|
|
|
1420
1483
|
} catch (err) {
|
|
1421
1484
|
console.error(
|
|
1422
1485
|
"[Channel] Mode check failed, delivering event (fail-open):",
|
|
1423
|
-
describeFetchError(err,
|
|
1486
|
+
describeFetchError(err, API_MODE, CHANNEL_MODE_FETCH_TIMEOUT_MS)
|
|
1424
1487
|
);
|
|
1425
1488
|
cachedModeAt = now;
|
|
1426
1489
|
}
|
|
@@ -1430,10 +1493,12 @@ var AWARENESS_DEBOUNCE_MS, MODE_CACHE_TTL_MS, cachedMode, cachedModeAt;
|
|
|
1430
1493
|
var init_event_bridge = __esm({
|
|
1431
1494
|
"src/channel/event-bridge.ts"() {
|
|
1432
1495
|
"use strict";
|
|
1496
|
+
init_api_paths();
|
|
1433
1497
|
init_cli_runtime();
|
|
1434
1498
|
init_constants();
|
|
1435
1499
|
init_types();
|
|
1436
1500
|
init_fetch_with_timeout();
|
|
1501
|
+
init_types3();
|
|
1437
1502
|
AWARENESS_DEBOUNCE_MS = 500;
|
|
1438
1503
|
MODE_CACHE_TTL_MS = 2e3;
|
|
1439
1504
|
cachedMode = "tandem";
|
|
@@ -1446,7 +1511,7 @@ import { createConnection } from "net";
|
|
|
1446
1511
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
1447
1512
|
import { StdioServerTransport as StdioServerTransport2 } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
1448
1513
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
1449
|
-
import { z } from "zod";
|
|
1514
|
+
import { z as z2 } from "zod";
|
|
1450
1515
|
async function runChannel(opts = {}) {
|
|
1451
1516
|
redirectConsoleToStderr();
|
|
1452
1517
|
const tandemUrl = resolveTandemUrl();
|
|
@@ -1501,7 +1566,7 @@ async function runChannel(opts = {}) {
|
|
|
1501
1566
|
const args2 = req.params.arguments;
|
|
1502
1567
|
try {
|
|
1503
1568
|
const res = await fetchWithTimeout(
|
|
1504
|
-
`${tandemUrl}
|
|
1569
|
+
`${tandemUrl}${API_CHANNEL_REPLY}`,
|
|
1505
1570
|
{
|
|
1506
1571
|
method: "POST",
|
|
1507
1572
|
headers: { "Content-Type": "application/json" },
|
|
@@ -1535,7 +1600,7 @@ async function runChannel(opts = {}) {
|
|
|
1535
1600
|
type: "text",
|
|
1536
1601
|
text: `Failed to send reply: ${describeFetchError(
|
|
1537
1602
|
err,
|
|
1538
|
-
|
|
1603
|
+
API_CHANNEL_REPLY,
|
|
1539
1604
|
CHANNEL_REPLY_FETCH_TIMEOUT_MS
|
|
1540
1605
|
)}`
|
|
1541
1606
|
}
|
|
@@ -1546,19 +1611,19 @@ async function runChannel(opts = {}) {
|
|
|
1546
1611
|
}
|
|
1547
1612
|
throw new Error(`Unknown tool: ${req.params.name}`);
|
|
1548
1613
|
});
|
|
1549
|
-
const PermissionRequestSchema =
|
|
1550
|
-
method:
|
|
1551
|
-
params:
|
|
1552
|
-
request_id:
|
|
1553
|
-
tool_name:
|
|
1554
|
-
description:
|
|
1555
|
-
input_preview:
|
|
1614
|
+
const PermissionRequestSchema = z2.object({
|
|
1615
|
+
method: z2.literal("notifications/claude/channel/permission_request"),
|
|
1616
|
+
params: z2.object({
|
|
1617
|
+
request_id: z2.string(),
|
|
1618
|
+
tool_name: z2.string(),
|
|
1619
|
+
description: z2.string(),
|
|
1620
|
+
input_preview: z2.string()
|
|
1556
1621
|
})
|
|
1557
1622
|
});
|
|
1558
1623
|
mcp.setNotificationHandler(PermissionRequestSchema, async ({ params }) => {
|
|
1559
1624
|
try {
|
|
1560
1625
|
const res = await fetchWithTimeout(
|
|
1561
|
-
`${tandemUrl}
|
|
1626
|
+
`${tandemUrl}${API_CHANNEL_PERMISSION}`,
|
|
1562
1627
|
{
|
|
1563
1628
|
method: "POST",
|
|
1564
1629
|
headers: { "Content-Type": "application/json" },
|
|
@@ -1579,7 +1644,7 @@ async function runChannel(opts = {}) {
|
|
|
1579
1644
|
} catch (err) {
|
|
1580
1645
|
console.error(
|
|
1581
1646
|
"[Channel] Failed to forward permission request:",
|
|
1582
|
-
describeFetchError(err,
|
|
1647
|
+
describeFetchError(err, API_CHANNEL_PERMISSION, CHANNEL_PERMISSION_FETCH_TIMEOUT_MS)
|
|
1583
1648
|
);
|
|
1584
1649
|
}
|
|
1585
1650
|
});
|
|
@@ -1605,7 +1670,7 @@ async function checkServerReachable(url, timeoutMs = 2e3) {
|
|
|
1605
1670
|
parsed = new URL(url);
|
|
1606
1671
|
} catch {
|
|
1607
1672
|
console.error(
|
|
1608
|
-
`[Channel] Invalid TANDEM_URL: "${url}" \u2014 expected format: http://
|
|
1673
|
+
`[Channel] Invalid TANDEM_URL: "${url}" \u2014 expected format: http://127.0.0.1:3479`
|
|
1609
1674
|
);
|
|
1610
1675
|
return false;
|
|
1611
1676
|
}
|
|
@@ -1630,6 +1695,7 @@ async function checkServerReachable(url, timeoutMs = 2e3) {
|
|
|
1630
1695
|
var init_run = __esm({
|
|
1631
1696
|
"src/channel/run.ts"() {
|
|
1632
1697
|
"use strict";
|
|
1698
|
+
init_api_paths();
|
|
1633
1699
|
init_cli_runtime();
|
|
1634
1700
|
init_constants();
|
|
1635
1701
|
init_fetch_with_timeout();
|
|
@@ -1739,7 +1805,7 @@ async function rotateToken() {
|
|
|
1739
1805
|
let serverRejected = false;
|
|
1740
1806
|
let serverRejectedStatus = 0;
|
|
1741
1807
|
try {
|
|
1742
|
-
const resp = await fetch(`${serverUrl}
|
|
1808
|
+
const resp = await fetch(`${serverUrl}${API_ROTATE_TOKEN}`, {
|
|
1743
1809
|
method: "POST",
|
|
1744
1810
|
headers: {
|
|
1745
1811
|
"Content-Type": "application/json",
|
|
@@ -1809,6 +1875,7 @@ async function rotateToken() {
|
|
|
1809
1875
|
var init_rotate_token = __esm({
|
|
1810
1876
|
"src/cli/rotate-token.ts"() {
|
|
1811
1877
|
"use strict";
|
|
1878
|
+
init_api_paths();
|
|
1812
1879
|
init_token_file();
|
|
1813
1880
|
init_cli_runtime();
|
|
1814
1881
|
init_setup();
|
|
@@ -1830,10 +1897,14 @@ function runStart() {
|
|
|
1830
1897
|
console.error("[Tandem] The installation may be corrupted. Try: npm install -g tandem-editor");
|
|
1831
1898
|
process.exit(1);
|
|
1832
1899
|
}
|
|
1900
|
+
console.error(
|
|
1901
|
+
"[Tandem] Browser distribution is deprecated; the Tauri desktop app is the primary form factor."
|
|
1902
|
+
);
|
|
1903
|
+
console.error("[Tandem] See https://github.com/bloknayrb/tandem/issues/477 for context.");
|
|
1833
1904
|
console.error("[Tandem] Starting server...");
|
|
1834
1905
|
const proc = spawn("node", [SERVER_DIST], {
|
|
1835
1906
|
stdio: "inherit",
|
|
1836
|
-
env:
|
|
1907
|
+
env: process.env
|
|
1837
1908
|
});
|
|
1838
1909
|
proc.on("error", (err) => {
|
|
1839
1910
|
console.error(`[Tandem] Failed to start server: ${err.message}`);
|
|
@@ -1872,7 +1943,7 @@ process.once("unhandledRejection", (reason) => {
|
|
|
1872
1943
|
`);
|
|
1873
1944
|
process.exit(1);
|
|
1874
1945
|
});
|
|
1875
|
-
var version = true ? "0.
|
|
1946
|
+
var version = true ? "0.12.0" : "0.0.0-dev";
|
|
1876
1947
|
var args = process.argv.slice(2);
|
|
1877
1948
|
var isStdioMode = args[0] === "mcp-stdio" || args[0] === "channel";
|
|
1878
1949
|
if (!isStdioMode) {
|