@askexenow/exe-os 0.8.37 → 0.8.39
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/README.md +17 -8
- package/dist/bin/backfill-conversations.js +112 -70
- package/dist/bin/backfill-responses.js +53 -18
- package/dist/bin/backfill-vectors.js +43 -16
- package/dist/bin/cleanup-stale-review-tasks.js +38 -16
- package/dist/bin/cli.js +790 -468
- package/dist/bin/exe-agent.js +19 -4
- package/dist/bin/exe-assign.js +46 -13
- package/dist/bin/exe-boot.js +288 -129
- package/dist/bin/exe-call.js +20 -10
- package/dist/bin/exe-cloud.js +135 -30
- package/dist/bin/exe-dispatch.js +1 -1
- package/dist/bin/exe-doctor.js +38 -16
- package/dist/bin/exe-export-behaviors.js +43 -21
- package/dist/bin/exe-forget.js +39 -17
- package/dist/bin/exe-gateway.js +159 -50
- package/dist/bin/exe-heartbeat.js +53 -31
- package/dist/bin/exe-kill.js +40 -18
- package/dist/bin/exe-launch-agent.js +109 -36
- package/dist/bin/exe-link.js +196 -87
- package/dist/bin/exe-new-employee.js +56 -17
- package/dist/bin/exe-pending-messages.js +47 -25
- package/dist/bin/exe-pending-notifications.js +38 -16
- package/dist/bin/exe-pending-reviews.js +51 -29
- package/dist/bin/exe-rename.js +21 -7
- package/dist/bin/exe-review.js +41 -13
- package/dist/bin/exe-search.js +57 -21
- package/dist/bin/exe-session-cleanup.js +67 -31
- package/dist/bin/exe-settings.js +63 -2
- package/dist/bin/exe-status.js +35 -13
- package/dist/bin/exe-team.js +35 -13
- package/dist/bin/git-sweep.js +45 -17
- package/dist/bin/graph-backfill.js +38 -16
- package/dist/bin/graph-export.js +38 -16
- package/dist/bin/install.js +10 -1
- package/dist/bin/scan-tasks.js +47 -19
- package/dist/bin/setup.js +444 -259
- package/dist/bin/shard-migrate.js +38 -16
- package/dist/bin/wiki-sync.js +40 -17
- package/dist/gateway/index.js +113 -48
- package/dist/hooks/bug-report-worker.js +66 -39
- package/dist/hooks/commit-complete.js +45 -17
- package/dist/hooks/error-recall.js +60 -20
- package/dist/hooks/exe-heartbeat-hook.js +3 -2
- package/dist/hooks/ingest-worker.js +174 -45
- package/dist/hooks/ingest.js +74 -28
- package/dist/hooks/instructions-loaded.js +46 -17
- package/dist/hooks/notification.js +44 -15
- package/dist/hooks/post-compact.js +44 -15
- package/dist/hooks/pre-compact.js +42 -14
- package/dist/hooks/pre-tool-use.js +59 -22
- package/dist/hooks/prompt-ingest-worker.js +75 -14
- package/dist/hooks/prompt-submit.js +75 -32
- package/dist/hooks/response-ingest-worker.js +76 -15
- package/dist/hooks/session-end.js +54 -22
- package/dist/hooks/session-start.js +57 -20
- package/dist/hooks/stop.js +44 -15
- package/dist/hooks/subagent-stop.js +44 -15
- package/dist/hooks/summary-worker.js +339 -106
- package/dist/index.js +94 -23
- package/dist/lib/cloud-sync.js +191 -80
- package/dist/lib/config.js +4 -1
- package/dist/lib/consolidation.js +5 -4
- package/dist/lib/database.js +1 -0
- package/dist/lib/device-registry.js +2 -1
- package/dist/lib/embedder.js +9 -1
- package/dist/lib/employee-templates.js +5 -0
- package/dist/lib/employees.js +11 -6
- package/dist/lib/exe-daemon-client.js +6 -1
- package/dist/lib/exe-daemon.js +95 -36
- package/dist/lib/hybrid-search.js +57 -21
- package/dist/lib/identity-templates.js +16 -7
- package/dist/lib/identity.js +1 -1
- package/dist/lib/keychain.js +2 -1
- package/dist/lib/license.js +56 -6
- package/dist/lib/messaging.js +1 -1
- package/dist/lib/reminders.js +2 -2
- package/dist/lib/schedules.js +38 -16
- package/dist/lib/skill-learning.js +1 -1
- package/dist/lib/store.js +44 -16
- package/dist/lib/tasks.js +1 -1
- package/dist/lib/tmux-routing.js +1 -1
- package/dist/mcp/server.js +280 -155
- package/dist/mcp/tools/complete-reminder.js +1 -1
- package/dist/mcp/tools/create-task.js +14 -6
- package/dist/mcp/tools/deactivate-behavior.js +2 -2
- package/dist/mcp/tools/list-reminders.js +1 -1
- package/dist/mcp/tools/list-tasks.js +36 -28
- package/dist/mcp/tools/send-message.js +1 -1
- package/dist/mcp/tools/update-task.js +1 -1
- package/dist/runtime/index.js +42 -8
- package/dist/tui/App.js +220 -99
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -461,6 +461,7 @@ async function ensureSchema() {
|
|
|
461
461
|
const client = getRawClient();
|
|
462
462
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
463
463
|
await client.execute("PRAGMA busy_timeout = 30000");
|
|
464
|
+
await client.execute("PRAGMA wal_autocheckpoint = 1000");
|
|
464
465
|
try {
|
|
465
466
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
466
467
|
} catch {
|
|
@@ -1284,7 +1285,7 @@ __export(config_exports, {
|
|
|
1284
1285
|
migrateConfig: () => migrateConfig,
|
|
1285
1286
|
saveConfig: () => saveConfig
|
|
1286
1287
|
});
|
|
1287
|
-
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
1288
|
+
import { readFile, writeFile, mkdir, chmod } from "fs/promises";
|
|
1288
1289
|
import { readFileSync as readFileSync3, existsSync as existsSync3, renameSync as renameSync2 } from "fs";
|
|
1289
1290
|
import path4 from "path";
|
|
1290
1291
|
import os4 from "os";
|
|
@@ -1410,6 +1411,9 @@ async function saveConfig(config2) {
|
|
|
1410
1411
|
await mkdir(dir, { recursive: true });
|
|
1411
1412
|
const configPath = path4.join(dir, "config.json");
|
|
1412
1413
|
await writeFile(configPath, JSON.stringify(config2, null, 2) + "\n");
|
|
1414
|
+
if (config2.cloud?.apiKey) {
|
|
1415
|
+
await chmod(configPath, 384);
|
|
1416
|
+
}
|
|
1413
1417
|
}
|
|
1414
1418
|
async function loadConfigFrom(configPath) {
|
|
1415
1419
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -3958,12 +3962,13 @@ var init_memory = __esm({
|
|
|
3958
3962
|
});
|
|
3959
3963
|
|
|
3960
3964
|
// src/lib/keychain.ts
|
|
3961
|
-
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod } from "fs/promises";
|
|
3965
|
+
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
3962
3966
|
import { existsSync as existsSync11 } from "fs";
|
|
3963
3967
|
import path15 from "path";
|
|
3968
|
+
import os7 from "os";
|
|
3964
3969
|
import crypto6 from "crypto";
|
|
3965
3970
|
function getKeyDir() {
|
|
3966
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path15.join(
|
|
3971
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path15.join(os7.homedir(), ".exe-os");
|
|
3967
3972
|
}
|
|
3968
3973
|
function getKeyPath() {
|
|
3969
3974
|
return path15.join(getKeyDir(), "master.key");
|
|
@@ -4020,7 +4025,7 @@ __export(shard_manager_exports, {
|
|
|
4020
4025
|
shardExists: () => shardExists
|
|
4021
4026
|
});
|
|
4022
4027
|
import path16 from "path";
|
|
4023
|
-
import { existsSync as existsSync12, mkdirSync as mkdirSync6 } from "fs";
|
|
4028
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
|
|
4024
4029
|
import { createClient as createClient2 } from "@libsql/client";
|
|
4025
4030
|
function initShardManager(encryptionKey) {
|
|
4026
4031
|
_encryptionKey = encryptionKey;
|
|
@@ -4059,7 +4064,6 @@ function shardExists(projectName) {
|
|
|
4059
4064
|
}
|
|
4060
4065
|
function listShards() {
|
|
4061
4066
|
if (!existsSync12(SHARDS_DIR)) return [];
|
|
4062
|
-
const { readdirSync: readdirSync3 } = __require("fs");
|
|
4063
4067
|
return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
4064
4068
|
}
|
|
4065
4069
|
async function ensureShardSchema(client) {
|
|
@@ -4265,6 +4269,28 @@ __export(store_exports, {
|
|
|
4265
4269
|
vectorToBlob: () => vectorToBlob,
|
|
4266
4270
|
writeMemory: () => writeMemory
|
|
4267
4271
|
});
|
|
4272
|
+
function isBusyError2(err) {
|
|
4273
|
+
if (err instanceof Error) {
|
|
4274
|
+
const msg = err.message.toLowerCase();
|
|
4275
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
4276
|
+
}
|
|
4277
|
+
return false;
|
|
4278
|
+
}
|
|
4279
|
+
async function retryOnBusy2(fn, label) {
|
|
4280
|
+
for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
|
|
4281
|
+
try {
|
|
4282
|
+
return await fn();
|
|
4283
|
+
} catch (err) {
|
|
4284
|
+
if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
|
|
4285
|
+
process.stderr.write(
|
|
4286
|
+
`[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
|
|
4287
|
+
`
|
|
4288
|
+
);
|
|
4289
|
+
await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
|
|
4290
|
+
}
|
|
4291
|
+
}
|
|
4292
|
+
throw new Error("unreachable");
|
|
4293
|
+
}
|
|
4268
4294
|
async function initStore(options) {
|
|
4269
4295
|
if (_flushTimer !== null) {
|
|
4270
4296
|
clearInterval(_flushTimer);
|
|
@@ -4293,14 +4319,17 @@ async function initStore(options) {
|
|
|
4293
4319
|
dbPath,
|
|
4294
4320
|
encryptionKey: hexKey
|
|
4295
4321
|
});
|
|
4296
|
-
await ensureSchema();
|
|
4322
|
+
await retryOnBusy2(() => ensureSchema(), "ensureSchema");
|
|
4297
4323
|
try {
|
|
4298
4324
|
const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
|
|
4299
4325
|
initShardManager2(hexKey);
|
|
4300
4326
|
} catch {
|
|
4301
4327
|
}
|
|
4302
4328
|
const client = getClient();
|
|
4303
|
-
const vResult = await
|
|
4329
|
+
const vResult = await retryOnBusy2(
|
|
4330
|
+
() => client.execute("SELECT MAX(version) as max_v FROM memories"),
|
|
4331
|
+
"version-query"
|
|
4332
|
+
);
|
|
4304
4333
|
_nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
4305
4334
|
}
|
|
4306
4335
|
function classifyTier(record) {
|
|
@@ -4343,6 +4372,12 @@ async function writeMemory(record) {
|
|
|
4343
4372
|
supersedes_id: record.supersedes_id ?? null
|
|
4344
4373
|
};
|
|
4345
4374
|
_pendingRecords.push(dbRow);
|
|
4375
|
+
const MAX_PENDING = 1e3;
|
|
4376
|
+
if (_pendingRecords.length > MAX_PENDING) {
|
|
4377
|
+
const dropped = _pendingRecords.length - MAX_PENDING;
|
|
4378
|
+
_pendingRecords = _pendingRecords.slice(-MAX_PENDING);
|
|
4379
|
+
console.warn(`[store] Dropped ${dropped} oldest pending records (overflow)`);
|
|
4380
|
+
}
|
|
4346
4381
|
if (_flushTimer === null) {
|
|
4347
4382
|
_flushTimer = setInterval(() => {
|
|
4348
4383
|
void flushBatch();
|
|
@@ -4674,7 +4709,7 @@ async function getMemoryCardinality(agentId) {
|
|
|
4674
4709
|
return 0;
|
|
4675
4710
|
}
|
|
4676
4711
|
}
|
|
4677
|
-
var _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
|
|
4712
|
+
var INIT_MAX_RETRIES, INIT_RETRY_DELAY_MS, _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
|
|
4678
4713
|
var init_store = __esm({
|
|
4679
4714
|
"src/lib/store.ts"() {
|
|
4680
4715
|
"use strict";
|
|
@@ -4682,6 +4717,8 @@ var init_store = __esm({
|
|
|
4682
4717
|
init_database();
|
|
4683
4718
|
init_keychain();
|
|
4684
4719
|
init_config();
|
|
4720
|
+
INIT_MAX_RETRIES = 3;
|
|
4721
|
+
INIT_RETRY_DELAY_MS = 1e3;
|
|
4685
4722
|
_pendingRecords = [];
|
|
4686
4723
|
_batchSize = 20;
|
|
4687
4724
|
_flushIntervalMs = 1e4;
|
|
@@ -5074,7 +5111,8 @@ async function gqlRequest(query, variables) {
|
|
|
5074
5111
|
"Content-Type": "application/json",
|
|
5075
5112
|
Authorization: `Bearer ${config.apiToken}`
|
|
5076
5113
|
},
|
|
5077
|
-
body: JSON.stringify({ query, variables })
|
|
5114
|
+
body: JSON.stringify({ query, variables }),
|
|
5115
|
+
signal: AbortSignal.timeout(3e4)
|
|
5078
5116
|
});
|
|
5079
5117
|
if (!res.ok) {
|
|
5080
5118
|
throw new Error(`CRM GraphQL request failed: ${res.status} ${res.statusText}`);
|
|
@@ -5302,6 +5340,10 @@ import path17 from "path";
|
|
|
5302
5340
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5303
5341
|
function handleData(chunk) {
|
|
5304
5342
|
_buffer += chunk.toString();
|
|
5343
|
+
if (_buffer.length > MAX_BUFFER) {
|
|
5344
|
+
_buffer = "";
|
|
5345
|
+
return;
|
|
5346
|
+
}
|
|
5305
5347
|
let newlineIdx;
|
|
5306
5348
|
while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
|
|
5307
5349
|
const line = _buffer.slice(0, newlineIdx).trim();
|
|
@@ -5609,7 +5651,7 @@ function disconnectClient() {
|
|
|
5609
5651
|
entry.resolve({ error: "Client disconnected" });
|
|
5610
5652
|
}
|
|
5611
5653
|
}
|
|
5612
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending;
|
|
5654
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending, MAX_BUFFER;
|
|
5613
5655
|
var init_exe_daemon_client = __esm({
|
|
5614
5656
|
"src/lib/exe-daemon-client.ts"() {
|
|
5615
5657
|
"use strict";
|
|
@@ -5626,6 +5668,7 @@ var init_exe_daemon_client = __esm({
|
|
|
5626
5668
|
_requestCount = 0;
|
|
5627
5669
|
HEALTH_CHECK_INTERVAL = 100;
|
|
5628
5670
|
_pending = /* @__PURE__ */ new Map();
|
|
5671
|
+
MAX_BUFFER = 1e7;
|
|
5629
5672
|
}
|
|
5630
5673
|
});
|
|
5631
5674
|
|
|
@@ -5715,12 +5758,30 @@ async function wikiFetch(config2, path20, method = "GET", body) {
|
|
|
5715
5758
|
const controller = new AbortController();
|
|
5716
5759
|
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS2);
|
|
5717
5760
|
try {
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5761
|
+
let response;
|
|
5762
|
+
try {
|
|
5763
|
+
response = await fetch(url, {
|
|
5764
|
+
method,
|
|
5765
|
+
headers,
|
|
5766
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
5767
|
+
signal: controller.signal
|
|
5768
|
+
});
|
|
5769
|
+
} catch {
|
|
5770
|
+
clearTimeout(timeout);
|
|
5771
|
+
const retryController = new AbortController();
|
|
5772
|
+
const retryTimeout = setTimeout(() => retryController.abort(), REQUEST_TIMEOUT_MS2);
|
|
5773
|
+
try {
|
|
5774
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
5775
|
+
response = await fetch(url, {
|
|
5776
|
+
method,
|
|
5777
|
+
headers,
|
|
5778
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
5779
|
+
signal: retryController.signal
|
|
5780
|
+
});
|
|
5781
|
+
} finally {
|
|
5782
|
+
clearTimeout(retryTimeout);
|
|
5783
|
+
}
|
|
5784
|
+
}
|
|
5724
5785
|
if (!response.ok) {
|
|
5725
5786
|
throw new Error(`Wiki API ${method} ${path20}: ${response.status} ${response.statusText}`);
|
|
5726
5787
|
}
|
|
@@ -9809,7 +9870,8 @@ var OllamaProvider = class {
|
|
|
9809
9870
|
const res = await fetch(`${this.host}/api/chat`, {
|
|
9810
9871
|
method: "POST",
|
|
9811
9872
|
headers: { "Content-Type": "application/json" },
|
|
9812
|
-
body: JSON.stringify(body)
|
|
9873
|
+
body: JSON.stringify(body),
|
|
9874
|
+
signal: AbortSignal.timeout(3e4)
|
|
9813
9875
|
});
|
|
9814
9876
|
if (!res.ok) {
|
|
9815
9877
|
throw new Error(`Ollama API error: ${res.status} ${await res.text()}`);
|
|
@@ -10237,7 +10299,7 @@ var SignalAdapter = class {
|
|
|
10237
10299
|
if (/^https?:\/\//i.test(trimmed)) {
|
|
10238
10300
|
return trimmed.replace(/\/+$/, "");
|
|
10239
10301
|
}
|
|
10240
|
-
return `
|
|
10302
|
+
return `https://${trimmed}`.replace(/\/+$/, "");
|
|
10241
10303
|
}
|
|
10242
10304
|
async connect(config2) {
|
|
10243
10305
|
this.baseUrl = this.normalizeBaseUrl(
|
|
@@ -10659,9 +10721,15 @@ var WebChatAdapter = class {
|
|
|
10659
10721
|
res.end(JSON.stringify({ error: "Not found" }));
|
|
10660
10722
|
}
|
|
10661
10723
|
async handleChatRequest(req, res) {
|
|
10724
|
+
const MAX_BODY_SIZE = 1048576;
|
|
10662
10725
|
let body = "";
|
|
10663
10726
|
for await (const chunk of req) {
|
|
10664
10727
|
body += chunk;
|
|
10728
|
+
if (body.length > MAX_BODY_SIZE) {
|
|
10729
|
+
res.writeHead(413, { "Content-Type": "application/json" });
|
|
10730
|
+
res.end(JSON.stringify({ error: "Request body too large" }));
|
|
10731
|
+
return;
|
|
10732
|
+
}
|
|
10665
10733
|
}
|
|
10666
10734
|
let parsed;
|
|
10667
10735
|
try {
|
|
@@ -11141,11 +11209,12 @@ var SlackAdapter = class {
|
|
|
11141
11209
|
// src/gateway/adapters/imessage.ts
|
|
11142
11210
|
import { execFile } from "child_process";
|
|
11143
11211
|
import { promisify } from "util";
|
|
11212
|
+
import os8 from "os";
|
|
11144
11213
|
import path18 from "path";
|
|
11145
11214
|
var execFileAsync = promisify(execFile);
|
|
11146
11215
|
var POLL_INTERVAL_MS = 5e3;
|
|
11147
11216
|
var MESSAGES_DB_PATH = path18.join(
|
|
11148
|
-
process.env.HOME ??
|
|
11217
|
+
process.env.HOME ?? os8.homedir(),
|
|
11149
11218
|
"Library/Messages/chat.db"
|
|
11150
11219
|
);
|
|
11151
11220
|
var IMessageAdapter = class {
|
|
@@ -11991,8 +12060,9 @@ async function ensureCRMContact(info) {
|
|
|
11991
12060
|
import { readFileSync as readFileSync12, writeFileSync as writeFileSync6, existsSync as existsSync14, mkdirSync as mkdirSync8 } from "fs";
|
|
11992
12061
|
import { randomUUID as randomUUID14 } from "crypto";
|
|
11993
12062
|
import path19 from "path";
|
|
11994
|
-
import
|
|
11995
|
-
var TRIGGERS_PATH = path19.join(
|
|
12063
|
+
import os9 from "os";
|
|
12064
|
+
var TRIGGERS_PATH = path19.join(os9.homedir(), ".exe-os", "triggers.json");
|
|
12065
|
+
var GRAPH_API_VERSION = "v21.0";
|
|
11996
12066
|
function substituteTemplate(template, record) {
|
|
11997
12067
|
return template.replace(
|
|
11998
12068
|
/\{\{(\w+(?:\.\w+)*)\}\}/g,
|
|
@@ -12070,7 +12140,7 @@ async function executeSendWhatsapp(params) {
|
|
|
12070
12140
|
const message = params.message ?? params.text;
|
|
12071
12141
|
if (!to || !message)
|
|
12072
12142
|
throw new Error("send_whatsapp requires 'to' and 'message' params");
|
|
12073
|
-
const url = `https://graph.facebook.com
|
|
12143
|
+
const url = `https://graph.facebook.com/${GRAPH_API_VERSION}/${account.phoneNumberId}/messages`;
|
|
12074
12144
|
const res = await fetch(url, {
|
|
12075
12145
|
method: "POST",
|
|
12076
12146
|
headers: {
|
|
@@ -12082,7 +12152,8 @@ async function executeSendWhatsapp(params) {
|
|
|
12082
12152
|
to,
|
|
12083
12153
|
type: "text",
|
|
12084
12154
|
text: { body: message }
|
|
12085
|
-
})
|
|
12155
|
+
}),
|
|
12156
|
+
signal: AbortSignal.timeout(3e4)
|
|
12086
12157
|
});
|
|
12087
12158
|
if (!res.ok) {
|
|
12088
12159
|
const errBody = await res.text();
|