dsclaw 0.1.8 → 0.1.9
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/dist/cli/index.js +159 -601
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +686 -1
- package/dist/index.js +156 -609
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/cli/index.js
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
|
|
3
|
+
// src/cli/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/cli/init.ts
|
|
7
|
+
import inquirer from "inquirer";
|
|
8
|
+
|
|
9
|
+
// src/gateway/config.ts
|
|
10
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
11
|
+
import { join } from "path";
|
|
12
|
+
import { homedir } from "os";
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
|
|
15
|
+
// src/shared/logger.ts
|
|
16
|
+
import pino from "pino";
|
|
6
17
|
|
|
7
18
|
// src/shared/tracer.ts
|
|
8
19
|
import { AsyncLocalStorage } from "async_hooks";
|
|
9
20
|
import { nanoid } from "nanoid";
|
|
21
|
+
var storage = new AsyncLocalStorage();
|
|
10
22
|
function runWithTrace(ctx, fn) {
|
|
11
23
|
const traceId = ctx.traceId ?? nanoid(12);
|
|
12
24
|
return storage.run({ traceId, ...ctx }, fn);
|
|
@@ -17,53 +29,27 @@ function getTraceContext() {
|
|
|
17
29
|
function getTraceId() {
|
|
18
30
|
return getTraceContext().traceId;
|
|
19
31
|
}
|
|
20
|
-
var storage;
|
|
21
|
-
var init_tracer = __esm({
|
|
22
|
-
"src/shared/tracer.ts"() {
|
|
23
|
-
"use strict";
|
|
24
|
-
storage = new AsyncLocalStorage();
|
|
25
|
-
}
|
|
26
|
-
});
|
|
27
32
|
|
|
28
33
|
// src/shared/logger.ts
|
|
29
|
-
|
|
34
|
+
var isPkg = typeof process.pkg !== "undefined";
|
|
35
|
+
var rootLogger = pino({
|
|
36
|
+
level: process.env["LOG_LEVEL"] ?? "info",
|
|
37
|
+
transport: !isPkg && process.env["NODE_ENV"] !== "production" ? { target: "pino-pretty", options: { colorize: true } } : void 0,
|
|
38
|
+
mixin() {
|
|
39
|
+
const ctx = getTraceContext();
|
|
40
|
+
return {
|
|
41
|
+
traceId: ctx.traceId,
|
|
42
|
+
...ctx.userId ? { userId: ctx.userId } : {},
|
|
43
|
+
...ctx.channelId ? { channelId: ctx.channelId } : {},
|
|
44
|
+
...ctx.agentId ? { agentId: ctx.agentId } : {}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
});
|
|
30
48
|
function createLogger(module) {
|
|
31
49
|
return rootLogger.child({ module });
|
|
32
50
|
}
|
|
33
|
-
var isPkg, rootLogger;
|
|
34
|
-
var init_logger = __esm({
|
|
35
|
-
"src/shared/logger.ts"() {
|
|
36
|
-
"use strict";
|
|
37
|
-
init_tracer();
|
|
38
|
-
isPkg = typeof process.pkg !== "undefined";
|
|
39
|
-
rootLogger = pino({
|
|
40
|
-
level: process.env["LOG_LEVEL"] ?? "info",
|
|
41
|
-
transport: !isPkg && process.env["NODE_ENV"] !== "production" ? { target: "pino-pretty", options: { colorize: true } } : void 0,
|
|
42
|
-
mixin() {
|
|
43
|
-
const ctx = getTraceContext();
|
|
44
|
-
return {
|
|
45
|
-
traceId: ctx.traceId,
|
|
46
|
-
...ctx.userId ? { userId: ctx.userId } : {},
|
|
47
|
-
...ctx.channelId ? { channelId: ctx.channelId } : {},
|
|
48
|
-
...ctx.agentId ? { agentId: ctx.agentId } : {}
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
// src/cli/index.ts
|
|
56
|
-
import { Command } from "commander";
|
|
57
|
-
|
|
58
|
-
// src/cli/init.ts
|
|
59
|
-
import inquirer from "inquirer";
|
|
60
51
|
|
|
61
52
|
// src/gateway/config.ts
|
|
62
|
-
init_logger();
|
|
63
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
64
|
-
import { join } from "path";
|
|
65
|
-
import { homedir } from "os";
|
|
66
|
-
import { z } from "zod";
|
|
67
53
|
var log = createLogger("config");
|
|
68
54
|
var CONFIG_DIR = join(homedir(), ".dsclaw");
|
|
69
55
|
var CONFIG_PATH = join(CONFIG_DIR, "config.json");
|
|
@@ -165,7 +151,6 @@ import {
|
|
|
165
151
|
} from "fs";
|
|
166
152
|
import { join as join2 } from "path";
|
|
167
153
|
import { hostname, userInfo } from "os";
|
|
168
|
-
init_logger();
|
|
169
154
|
var log2 = createLogger("user-session");
|
|
170
155
|
var ALG = "aes-256-gcm";
|
|
171
156
|
var IV_LEN = 12;
|
|
@@ -279,7 +264,6 @@ function isOnboarding(state) {
|
|
|
279
264
|
}
|
|
280
265
|
|
|
281
266
|
// src/dsers/auth.ts
|
|
282
|
-
init_logger();
|
|
283
267
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync3, chmodSync } from "fs";
|
|
284
268
|
import { dirname } from "path";
|
|
285
269
|
|
|
@@ -491,10 +475,6 @@ var DSersAuth = class {
|
|
|
491
475
|
}
|
|
492
476
|
};
|
|
493
477
|
|
|
494
|
-
// src/dsers/client.ts
|
|
495
|
-
init_logger();
|
|
496
|
-
init_tracer();
|
|
497
|
-
|
|
498
478
|
// src/shared/utils.ts
|
|
499
479
|
import { createHash as createHash2 } from "crypto";
|
|
500
480
|
function sleep(ms) {
|
|
@@ -510,7 +490,6 @@ function parseRetryAfter(value) {
|
|
|
510
490
|
}
|
|
511
491
|
|
|
512
492
|
// src/resilience/rate-limiter.ts
|
|
513
|
-
init_logger();
|
|
514
493
|
import pLimit from "p-limit";
|
|
515
494
|
var log4 = createLogger("rate-limiter");
|
|
516
495
|
var DEFAULT_OUTBOUND_LIMITS = {
|
|
@@ -792,7 +771,6 @@ function findAllChromium() {
|
|
|
792
771
|
}
|
|
793
772
|
|
|
794
773
|
// src/dsers/browser-auth.ts
|
|
795
|
-
init_logger();
|
|
796
774
|
var log6 = createLogger("dsers:browser-auth");
|
|
797
775
|
var DSERS_LOGIN_URL = "https://accounts.dsers.com/accounts/login";
|
|
798
776
|
var LOGIN_API_PATTERN = "/account-user-bff/v1/users/login";
|
|
@@ -1020,7 +998,6 @@ async function loginViaCDP() {
|
|
|
1020
998
|
}
|
|
1021
999
|
|
|
1022
1000
|
// src/agents/core-agent.ts
|
|
1023
|
-
init_logger();
|
|
1024
1001
|
import { generateText, streamText, tool as aiTool, stepCountIs } from "ai";
|
|
1025
1002
|
import { createOpenAI } from "@ai-sdk/openai";
|
|
1026
1003
|
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
@@ -1030,7 +1007,6 @@ import { z as z2 } from "zod";
|
|
|
1030
1007
|
// src/shared/audit.ts
|
|
1031
1008
|
import { appendFileSync, mkdirSync as mkdirSync4, existsSync as existsSync5 } from "fs";
|
|
1032
1009
|
import { join as join5 } from "path";
|
|
1033
|
-
init_tracer();
|
|
1034
1010
|
var AUDIT_DIR = join5(CONFIG_DIR, "audit");
|
|
1035
1011
|
function ensureAuditDir() {
|
|
1036
1012
|
if (!existsSync5(AUDIT_DIR)) {
|
|
@@ -1052,61 +1028,57 @@ function writeAuditLog(entry) {
|
|
|
1052
1028
|
}
|
|
1053
1029
|
}
|
|
1054
1030
|
|
|
1055
|
-
// src/dsers/product.ts
|
|
1056
|
-
async function getImportList(client, params) {
|
|
1057
|
-
const filtered = params ? Object.fromEntries(Object.entries(params).filter(([, v]) => v != null)) : {};
|
|
1058
|
-
return client.get("/dsers-product-bff/import-list", filtered);
|
|
1059
|
-
}
|
|
1060
|
-
async function getImportListItem(client, id) {
|
|
1061
|
-
return client.get(`/dsers-product-bff/import-list/${id}`);
|
|
1062
|
-
}
|
|
1063
|
-
async function getMyProducts(client, params) {
|
|
1064
|
-
return client.get("/dsers-product-bff/my-product", params);
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
1031
|
// src/agents/core-agent.ts
|
|
1068
|
-
import {
|
|
1069
|
-
import {
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
import {
|
|
1032
|
+
import { createMCPClient } from "@ai-sdk/mcp";
|
|
1033
|
+
import { Experimental_StdioMCPTransport as StdioTransport } from "@ai-sdk/mcp/mcp-stdio";
|
|
1034
|
+
|
|
1035
|
+
// src/dsers/mcp-token-bridge.ts
|
|
1036
|
+
import { randomBytes as randomBytes2, createCipheriv as createCipheriv2, createHash as createHash3 } from "crypto";
|
|
1037
|
+
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, chmodSync as chmodSync2 } from "fs";
|
|
1073
1038
|
import { join as join6 } from "path";
|
|
1074
|
-
import { homedir as homedir2 } from "os";
|
|
1075
|
-
var
|
|
1076
|
-
var
|
|
1077
|
-
var
|
|
1078
|
-
var
|
|
1079
|
-
|
|
1080
|
-
|
|
1039
|
+
import { homedir as homedir2, hostname as hostname2, userInfo as userInfo2 } from "os";
|
|
1040
|
+
var ALG2 = "aes-256-gcm";
|
|
1041
|
+
var IV_LEN2 = 12;
|
|
1042
|
+
var TAG_LEN2 = 16;
|
|
1043
|
+
var PACKAGE_SEED = "dsers-mcp-product-v1";
|
|
1044
|
+
var DSERS_BASE_URL = "https://bff-api-gw.dsers.com";
|
|
1045
|
+
function deriveKey2() {
|
|
1046
|
+
const host = hostname2();
|
|
1047
|
+
let user = "";
|
|
1081
1048
|
try {
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
for (const [short, entry] of Object.entries(raw)) {
|
|
1086
|
-
if (now - entry.ts < JOB_ID_TTL_MS) {
|
|
1087
|
-
map.set(short, entry.full);
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
} catch (err) {
|
|
1091
|
-
log7.warn({ err }, "Failed to load job ID map from disk");
|
|
1049
|
+
user = userInfo2().username;
|
|
1050
|
+
} catch {
|
|
1051
|
+
user = process.env["USER"] ?? process.env["USERNAME"] ?? "default";
|
|
1092
1052
|
}
|
|
1093
|
-
return
|
|
1053
|
+
return createHash3("sha256").update(`${PACKAGE_SEED}:${host}:${user}`).digest();
|
|
1054
|
+
}
|
|
1055
|
+
function encrypt2(data) {
|
|
1056
|
+
const key = deriveKey2();
|
|
1057
|
+
const iv = randomBytes2(IV_LEN2);
|
|
1058
|
+
const cipher = createCipheriv2(ALG2, key, iv, { authTagLength: TAG_LEN2 });
|
|
1059
|
+
const ct = Buffer.concat([cipher.update(data, "utf8"), cipher.final()]);
|
|
1060
|
+
const tag = cipher.getAuthTag();
|
|
1061
|
+
return Buffer.concat([iv, tag, ct]).toString("base64");
|
|
1094
1062
|
}
|
|
1095
|
-
function
|
|
1063
|
+
function writeMCPToken(sessionId, state) {
|
|
1064
|
+
const dir = join6(homedir2(), ".dsers-mcp");
|
|
1065
|
+
mkdirSync5(dir, { recursive: true, mode: 448 });
|
|
1066
|
+
const payload = JSON.stringify({
|
|
1067
|
+
session_id: sessionId,
|
|
1068
|
+
state,
|
|
1069
|
+
base_url: DSERS_BASE_URL,
|
|
1070
|
+
ts: Date.now()
|
|
1071
|
+
});
|
|
1072
|
+
const file = join6(dir, "credentials");
|
|
1073
|
+
writeFileSync4(file, encrypt2(payload), "utf-8");
|
|
1096
1074
|
try {
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
const now = Date.now();
|
|
1100
|
-
for (const [short, full] of map) {
|
|
1101
|
-
obj[short] = { full, ts: now };
|
|
1102
|
-
}
|
|
1103
|
-
const tmp = JOB_ID_MAP_FILE + ".tmp";
|
|
1104
|
-
writeFileSync4(tmp, JSON.stringify(obj));
|
|
1105
|
-
renameSync(tmp, JOB_ID_MAP_FILE);
|
|
1106
|
-
} catch (err) {
|
|
1107
|
-
log7.warn({ err }, "Failed to persist job ID map");
|
|
1075
|
+
chmodSync2(file, 384);
|
|
1076
|
+
} catch {
|
|
1108
1077
|
}
|
|
1109
1078
|
}
|
|
1079
|
+
|
|
1080
|
+
// src/agents/core-agent.ts
|
|
1081
|
+
var log7 = createLogger("agent:core");
|
|
1110
1082
|
function normalizeBaseUrl(url) {
|
|
1111
1083
|
if (!url) return void 0;
|
|
1112
1084
|
let u = url.trim();
|
|
@@ -1148,87 +1120,6 @@ function withToolAwareTimeout(promise, baseMs, isToolActive, label) {
|
|
|
1148
1120
|
});
|
|
1149
1121
|
return Promise.race([promise.finally(cleanup2), timeoutPromise]);
|
|
1150
1122
|
}
|
|
1151
|
-
var MCP_DROP_KEYS = /* @__PURE__ */ new Set([
|
|
1152
|
-
"image_urls",
|
|
1153
|
-
"description_html_snippet",
|
|
1154
|
-
"effective_rules_snapshot",
|
|
1155
|
-
"requested_rules",
|
|
1156
|
-
"original_draft",
|
|
1157
|
-
"resolved_source_url",
|
|
1158
|
-
"resolver_mode",
|
|
1159
|
-
"tags_before",
|
|
1160
|
-
"tags_after"
|
|
1161
|
-
]);
|
|
1162
|
-
function compactToolResult(result, jobIdMap) {
|
|
1163
|
-
if (!result || typeof result !== "object") return result;
|
|
1164
|
-
const isMcpPreview = "job_id" in result && ("title_after" in result || "status" in result);
|
|
1165
|
-
if (isMcpPreview) {
|
|
1166
|
-
const out2 = {};
|
|
1167
|
-
for (const [k, v] of Object.entries(result)) {
|
|
1168
|
-
if (MCP_DROP_KEYS.has(k)) continue;
|
|
1169
|
-
out2[k] = v;
|
|
1170
|
-
}
|
|
1171
|
-
if (out2.job_id && typeof out2.job_id === "string") {
|
|
1172
|
-
const dotIdx = out2.job_id.indexOf(".");
|
|
1173
|
-
if (dotIdx > 0) {
|
|
1174
|
-
const short = out2.job_id.slice(0, dotIdx);
|
|
1175
|
-
if (jobIdMap) jobIdMap.set(short, out2.job_id);
|
|
1176
|
-
out2.job_id = short;
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
if (out2.skus && Array.isArray(out2.skus) && out2.skus.length > 1) {
|
|
1180
|
-
const [header, ...rows] = out2.skus;
|
|
1181
|
-
out2.variant_summary = rows.slice(0, 5).map((row) => {
|
|
1182
|
-
const obj = {};
|
|
1183
|
-
header.forEach((key, i) => {
|
|
1184
|
-
obj[key] = row[i];
|
|
1185
|
-
});
|
|
1186
|
-
return obj;
|
|
1187
|
-
});
|
|
1188
|
-
if (rows.length > 5) {
|
|
1189
|
-
out2.variant_summary_note = `Showing 5 of ${rows.length} variants`;
|
|
1190
|
-
}
|
|
1191
|
-
delete out2.skus;
|
|
1192
|
-
}
|
|
1193
|
-
if (out2.title_after) {
|
|
1194
|
-
out2._title_modified = true;
|
|
1195
|
-
out2._display_title = out2.title_after;
|
|
1196
|
-
}
|
|
1197
|
-
if (out2.desc_changed) {
|
|
1198
|
-
out2._description_modified = true;
|
|
1199
|
-
}
|
|
1200
|
-
if (out2.warnings?.length > 5) out2.warnings = out2.warnings.slice(0, 5);
|
|
1201
|
-
if (out2.stores) {
|
|
1202
|
-
out2.stores = out2.stores.map((s) => ({
|
|
1203
|
-
store_ref: s.store_ref,
|
|
1204
|
-
display_name: s.display_name,
|
|
1205
|
-
platform: s.platform
|
|
1206
|
-
}));
|
|
1207
|
-
}
|
|
1208
|
-
if (out2.account_info) {
|
|
1209
|
-
const ai = out2.account_info;
|
|
1210
|
-
out2.account_info = { plan: ai.plan, limits: ai.limits, aliexpress_auth: ai.aliexpress_auth };
|
|
1211
|
-
}
|
|
1212
|
-
return out2;
|
|
1213
|
-
}
|
|
1214
|
-
const json = JSON.stringify(result);
|
|
1215
|
-
if (json.length <= 4e3) return result;
|
|
1216
|
-
const out = {};
|
|
1217
|
-
for (const [k, v] of Object.entries(result)) {
|
|
1218
|
-
if (MCP_DROP_KEYS.has(k)) continue;
|
|
1219
|
-
if (Array.isArray(v) && v.length > 20) {
|
|
1220
|
-
out[k] = v.slice(0, 20);
|
|
1221
|
-
out[`_${k}_truncated`] = `${v.length} total, showing first 20`;
|
|
1222
|
-
} else {
|
|
1223
|
-
out[k] = v;
|
|
1224
|
-
}
|
|
1225
|
-
}
|
|
1226
|
-
const outJson = JSON.stringify(out);
|
|
1227
|
-
if (outJson.length > 8e3) {
|
|
1228
|
-
return { _truncated: true, _original_size: json.length, summary: outJson.slice(0, 6e3) + "..." };
|
|
1229
|
-
}
|
|
1230
|
-
return out;
|
|
1231
|
-
}
|
|
1232
1123
|
function buildModel(llm) {
|
|
1233
1124
|
const baseURL = normalizeBaseUrl(llm.baseUrl);
|
|
1234
1125
|
if (baseURL || llm.provider === "other") {
|
|
@@ -1343,42 +1234,41 @@ var DSClawCoreAgent = class {
|
|
|
1343
1234
|
memory;
|
|
1344
1235
|
llm;
|
|
1345
1236
|
customTools = /* @__PURE__ */ new Map();
|
|
1346
|
-
|
|
1347
|
-
jobIdMap = loadJobIdMap();
|
|
1237
|
+
mcpClient = null;
|
|
1348
1238
|
mcpAvailable = false;
|
|
1349
1239
|
authCallback;
|
|
1350
|
-
constructor(dsers, memory, llm,
|
|
1240
|
+
constructor(dsers, memory, llm, authCallback) {
|
|
1351
1241
|
this.dsers = dsers;
|
|
1352
1242
|
this.memory = memory;
|
|
1353
1243
|
this.llm = llm;
|
|
1354
1244
|
this.authCallback = authCallback;
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
}
|
|
1245
|
+
}
|
|
1246
|
+
async initMCP(dsersSession) {
|
|
1247
|
+
try {
|
|
1248
|
+
writeMCPToken(dsersSession.sessionId, dsersSession.state);
|
|
1249
|
+
this.mcpClient = await createMCPClient({
|
|
1250
|
+
transport: new StdioTransport({
|
|
1251
|
+
command: "npx",
|
|
1252
|
+
args: ["-y", "@lofder/dsers-mcp-product"]
|
|
1253
|
+
})
|
|
1254
|
+
});
|
|
1255
|
+
this.mcpAvailable = true;
|
|
1256
|
+
log7.info("MCP client connected via stdio");
|
|
1257
|
+
} catch (err) {
|
|
1258
|
+
log7.warn({ err }, "Failed to connect MCP \u2014 product tools unavailable");
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
async destroy() {
|
|
1262
|
+
if (this.mcpClient) {
|
|
1263
|
+
await this.mcpClient.close();
|
|
1264
|
+
this.mcpClient = null;
|
|
1265
|
+
this.mcpAvailable = false;
|
|
1365
1266
|
}
|
|
1366
1267
|
}
|
|
1367
1268
|
getSystemPrompt() {
|
|
1368
1269
|
const rule = "ALWAYS respond in the same language as the user's most recent message. If they write Chinese, respond in Chinese. If English, respond in English. Never switch languages unless the user does.";
|
|
1369
1270
|
return SYSTEM_PROMPT.replace("{{LANGUAGE_RULE}}", `Language rule: ${rule}`);
|
|
1370
1271
|
}
|
|
1371
|
-
shortenJobId(fullId) {
|
|
1372
|
-
const dotIdx = fullId.indexOf(".");
|
|
1373
|
-
if (dotIdx <= 0) return fullId;
|
|
1374
|
-
const short = fullId.slice(0, dotIdx);
|
|
1375
|
-
this.jobIdMap.set(short, fullId);
|
|
1376
|
-
persistJobIdMap(this.jobIdMap);
|
|
1377
|
-
return short;
|
|
1378
|
-
}
|
|
1379
|
-
resolveJobId(maybeShort) {
|
|
1380
|
-
return this.jobIdMap.get(maybeShort) ?? maybeShort;
|
|
1381
|
-
}
|
|
1382
1272
|
registerTool(t2) {
|
|
1383
1273
|
this.customTools.set(t2.name, t2);
|
|
1384
1274
|
}
|
|
@@ -1405,7 +1295,7 @@ ${memories.map((m2) => `- ${m2.content}`).join("\n")}` : "";
|
|
|
1405
1295
|
),
|
|
1406
1296
|
{ role: "user", content: message }
|
|
1407
1297
|
];
|
|
1408
|
-
const tools = this.buildAITools(context);
|
|
1298
|
+
const tools = await this.buildAITools(context);
|
|
1409
1299
|
log7.info(
|
|
1410
1300
|
{ userId: context.userId, messageLen: message.length, toolCount: Object.keys(tools).length },
|
|
1411
1301
|
"Processing message"
|
|
@@ -1457,9 +1347,9 @@ ${memories.map((m2) => `- ${m2.content}`).join("\n")}` : "";
|
|
|
1457
1347
|
processStream(message, context, onStatus, callbacks, attachments) {
|
|
1458
1348
|
const model = buildModel(this.llm);
|
|
1459
1349
|
const memories = this.memory.search(message, { userId: context.userId, limit: 5 }).catch(() => []);
|
|
1460
|
-
const
|
|
1350
|
+
const toolsPromise = this.buildAITools(context);
|
|
1461
1351
|
const self = this;
|
|
1462
|
-
const streamPromise = memories.then((mems) => {
|
|
1352
|
+
const streamPromise = Promise.all([memories, toolsPromise]).then(([mems, tools]) => {
|
|
1463
1353
|
const memoryContext = mems.length > 0 ? `
|
|
1464
1354
|
|
|
1465
1355
|
Relevant memories:
|
|
@@ -1595,207 +1485,13 @@ ${mems.map((m2) => `- ${m2.content}`).join("\n")}` : "";
|
|
|
1595
1485
|
});
|
|
1596
1486
|
return { textStream, response };
|
|
1597
1487
|
}
|
|
1598
|
-
buildAITools(
|
|
1599
|
-
const dsers = this.dsers;
|
|
1600
|
-
const svc = this.importService;
|
|
1601
|
-
const jmap = this.jobIdMap;
|
|
1602
|
-
const resolveJid = (id) => this.resolveJobId(id);
|
|
1603
|
-
const audit = (action, target, params, result = "pending") => writeAuditLog({ userId: context.userId, agentId: this.id, action, target, params, result });
|
|
1488
|
+
async buildAITools(_context) {
|
|
1604
1489
|
const tools = {};
|
|
1605
|
-
if (
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
inputSchema: z2.object({
|
|
1609
|
-
target_store: z2.string().optional().describe("Filter by store ID or name")
|
|
1610
|
-
}),
|
|
1611
|
-
execute: async (input) => {
|
|
1612
|
-
audit("dsers_store_discover", "mcp");
|
|
1613
|
-
const result = await withTimeout(svc.getRuleCapabilities(input), 6e4, "Store discovery");
|
|
1614
|
-
audit("dsers_store_discover", "mcp", void 0, "success");
|
|
1615
|
-
return compactToolResult(result, jmap);
|
|
1616
|
-
}
|
|
1617
|
-
});
|
|
1618
|
-
tools.dsers_product_import = aiTool({
|
|
1619
|
-
description: "Import product by URL or re-apply rules to existing job. Modes: (1) source_url for new import, (2) job_id+rules_json to update rules, (3) job_id alone to refresh. Expired job_id \u2192 re-import with source_url. Returns: job_id, title (or title_before+title_after), sell_price, cost, variants, blocked, warnings.",
|
|
1620
|
-
inputSchema: z2.object({
|
|
1621
|
-
source_url: z2.string().optional().describe("Supplier product URL"),
|
|
1622
|
-
source_urls_json: z2.string().optional().describe("Batch: JSON array of URLs"),
|
|
1623
|
-
job_id: z2.string().optional().describe("Existing job ID for rule updates"),
|
|
1624
|
-
source_hint: z2.string().optional().describe("auto|aliexpress|alibaba|accio"),
|
|
1625
|
-
country: z2.string().default("US").describe("Country code"),
|
|
1626
|
-
target_store: z2.string().optional().describe("Store ID or name"),
|
|
1627
|
-
visibility_mode: z2.string().optional().describe("backend_only|sell_immediately"),
|
|
1628
|
-
rules_json: z2.string().optional().describe(
|
|
1629
|
-
"JSON rules. Pricing: fixed_price({fixed_price:9.99}), multiplier({multiplier:2}), fixed_markup({fixed_markup:5}). Content: title_override, description_override_html. Images: keep_first_n, drop_indexes, add_urls. variant_overrides: [{match,sell_price,compare_at_price,stock}]. option_edits: [{action,option_name,value_name?,new_name?}]."
|
|
1630
|
-
)
|
|
1631
|
-
}),
|
|
1632
|
-
execute: async (input) => {
|
|
1633
|
-
audit("dsers_product_import", "mcp", input);
|
|
1634
|
-
const payload = {};
|
|
1635
|
-
if (input.job_id && !input.source_url && !input.source_urls_json) {
|
|
1636
|
-
payload.job_id = resolveJid(input.job_id);
|
|
1637
|
-
if (input.rules_json) {
|
|
1638
|
-
try {
|
|
1639
|
-
payload.rules = JSON.parse(input.rules_json);
|
|
1640
|
-
} catch {
|
|
1641
|
-
throw new Error(
|
|
1642
|
-
'Invalid JSON in rules_json. Expected: {"pricing":{"mode":"fixed_price","fixed_price":9.99}} or {"content":{"title_override":"New Title"}}'
|
|
1643
|
-
);
|
|
1644
|
-
}
|
|
1645
|
-
} else {
|
|
1646
|
-
payload._keep_existing_rules = true;
|
|
1647
|
-
}
|
|
1648
|
-
if (input.target_store) payload.target_store = input.target_store;
|
|
1649
|
-
if (input.visibility_mode) payload.visibility_mode = input.visibility_mode;
|
|
1650
|
-
const result2 = await withTimeout(svc.reapplyRules(payload), 12e4, "Rule reapply");
|
|
1651
|
-
audit("dsers_product_import", "mcp", void 0, "success");
|
|
1652
|
-
return compactToolResult(result2, jmap);
|
|
1653
|
-
}
|
|
1654
|
-
if (input.source_url) payload.source_url = input.source_url;
|
|
1655
|
-
if (input.source_urls_json) {
|
|
1656
|
-
try {
|
|
1657
|
-
payload.source_urls = JSON.parse(input.source_urls_json);
|
|
1658
|
-
} catch {
|
|
1659
|
-
throw new Error("Invalid JSON in source_urls_json.");
|
|
1660
|
-
}
|
|
1661
|
-
}
|
|
1662
|
-
if (input.source_hint) payload.source_hint = input.source_hint;
|
|
1663
|
-
if (input.country) payload.country = input.country;
|
|
1664
|
-
if (input.target_store) payload.target_store = input.target_store;
|
|
1665
|
-
payload.visibility_mode = input.visibility_mode || "backend_only";
|
|
1666
|
-
if (input.rules_json) {
|
|
1667
|
-
try {
|
|
1668
|
-
payload.rules = JSON.parse(input.rules_json);
|
|
1669
|
-
} catch {
|
|
1670
|
-
throw new Error("Invalid JSON in rules_json.");
|
|
1671
|
-
}
|
|
1672
|
-
}
|
|
1673
|
-
const result = await withTimeout(svc.prepareImportCandidate(payload), 12e4, "Product import");
|
|
1674
|
-
audit("dsers_product_import", "mcp", void 0, "success");
|
|
1675
|
-
return compactToolResult(result, jmap);
|
|
1676
|
-
}
|
|
1677
|
-
});
|
|
1678
|
-
tools.dsers_product_preview = aiTool({
|
|
1679
|
-
description: "Reload preview for an imported job. Returns prices, variants, images.",
|
|
1680
|
-
inputSchema: z2.object({
|
|
1681
|
-
job_id: z2.string().describe("Job ID from dsers_product_import"),
|
|
1682
|
-
variant_offset: z2.number().optional().describe("Start index for variant pagination"),
|
|
1683
|
-
variant_limit: z2.number().optional().describe("Max variants to return")
|
|
1684
|
-
}),
|
|
1685
|
-
execute: async (input) => {
|
|
1686
|
-
audit("dsers_product_preview", "mcp", input);
|
|
1687
|
-
const result = await withTimeout(svc.getImportPreview({ ...input, job_id: resolveJid(input.job_id) }), 3e4, "Preview");
|
|
1688
|
-
audit("dsers_product_preview", "mcp", void 0, "success");
|
|
1689
|
-
return compactToolResult(result, jmap);
|
|
1690
|
-
}
|
|
1691
|
-
});
|
|
1692
|
-
tools.dsers_store_push = aiTool({
|
|
1693
|
-
description: "Push product(s) to store(s). Show confirmation checklist and get user approval first. May return blocked (shipping_profile, pricing_rule) or warnings. Use push_options_json with shipping_profile_name when store has multiple shipping profiles.",
|
|
1694
|
-
inputSchema: z2.object({
|
|
1695
|
-
job_id: z2.string().optional().describe("Job ID"),
|
|
1696
|
-
job_ids_json: z2.string().optional().describe("Batch: JSON array of job IDs"),
|
|
1697
|
-
target_store: z2.string().optional().describe("Store ID or name from dsers_store_discover"),
|
|
1698
|
-
target_stores_json: z2.string().optional().describe("Multi-store: JSON array of store names"),
|
|
1699
|
-
visibility_mode: z2.string().optional().describe("backend_only|sell_immediately"),
|
|
1700
|
-
force_push: z2.boolean().optional().describe("Override safety checks"),
|
|
1701
|
-
push_options_json: z2.string().optional().describe(
|
|
1702
|
-
'Push config JSON. Include shipping_profile_name when multiple profiles exist. E.g. {"shipping_profile_name":"General Profile","pricing_rule_behavior":"apply_store_pricing_rule"}'
|
|
1703
|
-
)
|
|
1704
|
-
}),
|
|
1705
|
-
execute: async (input) => {
|
|
1706
|
-
audit("dsers_store_push", "mcp", input);
|
|
1707
|
-
const payload = {};
|
|
1708
|
-
if (input.job_ids_json) {
|
|
1709
|
-
try {
|
|
1710
|
-
const ids = JSON.parse(input.job_ids_json);
|
|
1711
|
-
payload.job_ids = ids.map(resolveJid);
|
|
1712
|
-
} catch {
|
|
1713
|
-
throw new Error("Invalid JSON in job_ids_json");
|
|
1714
|
-
}
|
|
1715
|
-
} else if (input.job_id) {
|
|
1716
|
-
payload.job_id = resolveJid(input.job_id);
|
|
1717
|
-
}
|
|
1718
|
-
if (input.target_store) payload.target_store = input.target_store;
|
|
1719
|
-
if (input.target_stores_json) {
|
|
1720
|
-
try {
|
|
1721
|
-
payload.target_stores = JSON.parse(input.target_stores_json);
|
|
1722
|
-
} catch {
|
|
1723
|
-
throw new Error("Invalid JSON in target_stores_json");
|
|
1724
|
-
}
|
|
1725
|
-
}
|
|
1726
|
-
if (input.visibility_mode) payload.visibility_mode = input.visibility_mode;
|
|
1727
|
-
if (input.force_push) payload.force_push = true;
|
|
1728
|
-
if (input.push_options_json) {
|
|
1729
|
-
try {
|
|
1730
|
-
payload.push_options = JSON.parse(input.push_options_json);
|
|
1731
|
-
} catch {
|
|
1732
|
-
throw new Error("Invalid JSON in push_options_json");
|
|
1733
|
-
}
|
|
1734
|
-
}
|
|
1735
|
-
const result = await withTimeout(svc.confirmPushToStore(payload), 18e4, "Push to store");
|
|
1736
|
-
audit("dsers_store_push", "mcp", void 0, "success");
|
|
1737
|
-
return compactToolResult(result, jmap);
|
|
1738
|
-
}
|
|
1739
|
-
});
|
|
1740
|
-
tools.dsers_rules_validate = aiTool({
|
|
1741
|
-
description: "Validate pricing/content/image rules before importing.",
|
|
1742
|
-
inputSchema: z2.object({
|
|
1743
|
-
rules_json: z2.string().describe("Rules as JSON string"),
|
|
1744
|
-
target_store: z2.string().optional().describe("Store ID or name")
|
|
1745
|
-
}),
|
|
1746
|
-
execute: async (input) => {
|
|
1747
|
-
audit("dsers_rules_validate", "mcp");
|
|
1748
|
-
let rules;
|
|
1749
|
-
try {
|
|
1750
|
-
rules = JSON.parse(input.rules_json);
|
|
1751
|
-
} catch {
|
|
1752
|
-
throw new Error("Invalid JSON in rules_json");
|
|
1753
|
-
}
|
|
1754
|
-
const result = await withTimeout(svc.validateRules({ rules, target_store: input.target_store ?? null }), 3e4, "Rule validation");
|
|
1755
|
-
audit("dsers_rules_validate", "mcp", void 0, "success");
|
|
1756
|
-
return compactToolResult(result, jmap);
|
|
1757
|
-
}
|
|
1758
|
-
});
|
|
1759
|
-
tools.dsers_job_status = aiTool({
|
|
1760
|
-
description: "Check job status: preview_ready \u2192 push_requested \u2192 completed/failed.",
|
|
1761
|
-
inputSchema: z2.object({
|
|
1762
|
-
job_id: z2.string().describe("Job ID")
|
|
1763
|
-
}),
|
|
1764
|
-
execute: async (input) => {
|
|
1765
|
-
audit("dsers_job_status", "mcp", input);
|
|
1766
|
-
const result = await withTimeout(svc.getJobStatus({ ...input, job_id: resolveJid(input.job_id) }), 3e4, "Job status");
|
|
1767
|
-
audit("dsers_job_status", "mcp", void 0, "success");
|
|
1768
|
-
return compactToolResult(result, jmap);
|
|
1769
|
-
}
|
|
1770
|
-
});
|
|
1771
|
-
tools.dsers_product_visibility = aiTool({
|
|
1772
|
-
description: "Set job visibility: backend_only (draft) or sell_immediately (live).",
|
|
1773
|
-
inputSchema: z2.object({
|
|
1774
|
-
job_id: z2.string().describe("Job ID"),
|
|
1775
|
-
visibility_mode: z2.string().describe("backend_only|sell_immediately")
|
|
1776
|
-
}),
|
|
1777
|
-
execute: async (input) => {
|
|
1778
|
-
audit("dsers_product_visibility", "mcp", input);
|
|
1779
|
-
const result = await withTimeout(svc.setProductVisibility({ ...input, job_id: resolveJid(input.job_id) }), 3e4, "Set visibility");
|
|
1780
|
-
audit("dsers_product_visibility", "mcp", void 0, "success");
|
|
1781
|
-
return compactToolResult(result, jmap);
|
|
1782
|
-
}
|
|
1783
|
-
});
|
|
1784
|
-
tools.dsers_product_delete = aiTool({
|
|
1785
|
-
description: "Delete product from import list. Call without confirm first, then with confirm=true after user approves.",
|
|
1786
|
-
inputSchema: z2.object({
|
|
1787
|
-
import_item_id: z2.string().describe("Item ID from dsers_import_list_search"),
|
|
1788
|
-
confirm: z2.boolean().optional().describe("true only after user approves")
|
|
1789
|
-
}),
|
|
1790
|
-
execute: async (input) => {
|
|
1791
|
-
audit("dsers_product_delete", "mcp", input);
|
|
1792
|
-
const result = await withTimeout(svc.deleteImportItem(input), 3e4, "Delete import item");
|
|
1793
|
-
audit("dsers_product_delete", "mcp", void 0, "success");
|
|
1794
|
-
return compactToolResult(result, jmap);
|
|
1795
|
-
}
|
|
1796
|
-
});
|
|
1490
|
+
if (this.mcpClient) {
|
|
1491
|
+
const mcpTools = await this.mcpClient.tools();
|
|
1492
|
+
Object.assign(tools, mcpTools);
|
|
1797
1493
|
} else {
|
|
1798
|
-
log7.warn("MCP
|
|
1494
|
+
log7.warn("MCP client not connected \u2014 no product tools available");
|
|
1799
1495
|
}
|
|
1800
1496
|
if (this.authCallback) {
|
|
1801
1497
|
tools.dsers_authorize = aiTool({
|
|
@@ -1807,139 +1503,13 @@ ${mems.map((m2) => `- ${m2.content}`).join("\n")}` : "";
|
|
|
1807
1503
|
}
|
|
1808
1504
|
});
|
|
1809
1505
|
}
|
|
1810
|
-
tools.dsers_import_list_search = aiTool({
|
|
1811
|
-
description: "Search import list. Returns items with id, source_url, cost_range, sell_price_range. Use source_url with dsers_product_import for modifications.",
|
|
1812
|
-
inputSchema: z2.object({
|
|
1813
|
-
page: z2.number().optional().describe("Page number (default 1)"),
|
|
1814
|
-
pageSize: z2.number().optional().describe("Items per page (default 20)"),
|
|
1815
|
-
keyword: z2.string().optional().describe("Search keyword")
|
|
1816
|
-
}),
|
|
1817
|
-
execute: async (input) => {
|
|
1818
|
-
audit("dsers_import_list_search", "dsers:product", input);
|
|
1819
|
-
const result = await withTimeout(getImportList(dsers, input), 3e4, "Search import list");
|
|
1820
|
-
audit("dsers_import_list_search", "dsers:product", void 0, "success");
|
|
1821
|
-
const items = result?.data?.list ?? result?.data ?? [];
|
|
1822
|
-
if (Array.isArray(items) && items.length > 0) {
|
|
1823
|
-
const enriched = await Promise.allSettled(
|
|
1824
|
-
items.slice(0, 10).map(async (item) => {
|
|
1825
|
-
const itemId = item.id ?? item.importListId;
|
|
1826
|
-
if (!itemId) return item;
|
|
1827
|
-
try {
|
|
1828
|
-
const detail = await withTimeout(
|
|
1829
|
-
getImportListItem(dsers, String(itemId)),
|
|
1830
|
-
15e3,
|
|
1831
|
-
"Item detail"
|
|
1832
|
-
);
|
|
1833
|
-
const detailData = detail?.data ?? detail;
|
|
1834
|
-
const variants = detailData?.variants ?? detailData?.skuList ?? detailData?.variantList ?? [];
|
|
1835
|
-
if (!Array.isArray(variants) || !variants.length) return item;
|
|
1836
|
-
const sellPrices = [];
|
|
1837
|
-
const costPrices = [];
|
|
1838
|
-
for (const v of variants) {
|
|
1839
|
-
const rawSell = Number(v.sellPrice ?? v.salePrice ?? v.price);
|
|
1840
|
-
const rawCost = Number(v.supplierPrice ?? v.buyPrice ?? v.cost);
|
|
1841
|
-
const sell = rawSell > 100 ? rawSell / 100 : rawSell;
|
|
1842
|
-
const cost = rawCost > 100 ? rawCost / 100 : rawCost;
|
|
1843
|
-
if (Number.isFinite(sell) && sell > 0) sellPrices.push(sell);
|
|
1844
|
-
if (Number.isFinite(cost) && cost > 0) costPrices.push(cost);
|
|
1845
|
-
}
|
|
1846
|
-
const enrichedItem = { ...item };
|
|
1847
|
-
const supplyProductId = detailData?.supplyProductId ?? item?.supplyProductId;
|
|
1848
|
-
const supplyAppId = Number(detailData?.supplyAppId ?? item?.supplyAppId ?? 1);
|
|
1849
|
-
if (supplyProductId) {
|
|
1850
|
-
if (supplyAppId === 2) {
|
|
1851
|
-
enrichedItem.source_url = `https://www.temu.com/product/${supplyProductId}.html`;
|
|
1852
|
-
} else if (supplyAppId === 3) {
|
|
1853
|
-
enrichedItem.source_url = `https://detail.1688.com/offer/${supplyProductId}.html`;
|
|
1854
|
-
} else {
|
|
1855
|
-
enrichedItem.source_url = `https://www.aliexpress.com/item/${supplyProductId}.html`;
|
|
1856
|
-
}
|
|
1857
|
-
}
|
|
1858
|
-
if (costPrices.length) {
|
|
1859
|
-
enrichedItem.cost_range = `$${Math.min(...costPrices).toFixed(2)} \u2013 $${Math.max(...costPrices).toFixed(2)}`;
|
|
1860
|
-
}
|
|
1861
|
-
if (sellPrices.length) {
|
|
1862
|
-
enrichedItem.sell_price_range = `$${Math.min(...sellPrices).toFixed(2)} \u2013 $${Math.max(...sellPrices).toFixed(2)}`;
|
|
1863
|
-
enrichedItem.no_markup = sellPrices.every((s, i) => Math.abs(s - (costPrices[i] ?? s)) < 0.01);
|
|
1864
|
-
}
|
|
1865
|
-
enrichedItem.variant_count = variants.length;
|
|
1866
|
-
let totalStock = 0;
|
|
1867
|
-
const lowStockVariants = [];
|
|
1868
|
-
for (const v of variants) {
|
|
1869
|
-
const stock = Number(v.stock ?? v.quantity ?? v.inventory ?? 0);
|
|
1870
|
-
const safeStock = Number.isFinite(stock) ? stock : 0;
|
|
1871
|
-
totalStock += safeStock;
|
|
1872
|
-
if (safeStock <= 5) {
|
|
1873
|
-
lowStockVariants.push({
|
|
1874
|
-
id: String(v.id ?? v.variantId ?? v.skuId ?? ""),
|
|
1875
|
-
sku: String(v.sku ?? v.skuAttr ?? v.optionName ?? v.title ?? ""),
|
|
1876
|
-
stock: safeStock
|
|
1877
|
-
});
|
|
1878
|
-
}
|
|
1879
|
-
}
|
|
1880
|
-
enrichedItem.total_stock = totalStock;
|
|
1881
|
-
if (lowStockVariants.length > 0) {
|
|
1882
|
-
enrichedItem.low_stock_variants = lowStockVariants;
|
|
1883
|
-
enrichedItem.low_stock_warning = `${lowStockVariants.length} of ${variants.length} variants have stock \u2264 5`;
|
|
1884
|
-
}
|
|
1885
|
-
return enrichedItem;
|
|
1886
|
-
} catch (err) {
|
|
1887
|
-
log7.warn({ err, itemId }, "Failed to enrich import list item");
|
|
1888
|
-
return item;
|
|
1889
|
-
}
|
|
1890
|
-
})
|
|
1891
|
-
);
|
|
1892
|
-
const enrichedItems = enriched.map((r) => r.status === "fulfilled" ? r.value : items[0]);
|
|
1893
|
-
const cleanItems = enrichedItems.map((item) => ({
|
|
1894
|
-
id: item.id ?? item.importListId,
|
|
1895
|
-
title: item.title ?? "(untitled)",
|
|
1896
|
-
source_url: item.source_url,
|
|
1897
|
-
cost_range: item.cost_range,
|
|
1898
|
-
sell_price_range: item.sell_price_range,
|
|
1899
|
-
no_markup: item.no_markup,
|
|
1900
|
-
variant_count: item.variant_count,
|
|
1901
|
-
total_stock: item.total_stock,
|
|
1902
|
-
low_stock_warning: item.low_stock_warning,
|
|
1903
|
-
low_stock_variants: item.low_stock_variants
|
|
1904
|
-
}));
|
|
1905
|
-
return { items: cleanItems, total: result?.data?.total ?? cleanItems.length };
|
|
1906
|
-
}
|
|
1907
|
-
const rawItems = result?.data ?? [];
|
|
1908
|
-
if (Array.isArray(rawItems)) {
|
|
1909
|
-
return { items: rawItems.map((item) => ({
|
|
1910
|
-
id: item.id ?? item.importListId,
|
|
1911
|
-
title: item.title ?? "(untitled)"
|
|
1912
|
-
})), total: rawItems.length };
|
|
1913
|
-
}
|
|
1914
|
-
return compactToolResult(result);
|
|
1915
|
-
}
|
|
1916
|
-
});
|
|
1917
|
-
tools.dsers_my_products = aiTool({
|
|
1918
|
-
description: "Get products already pushed to stores.",
|
|
1919
|
-
inputSchema: z2.object({
|
|
1920
|
-
page: z2.number().optional(),
|
|
1921
|
-
pageSize: z2.number().optional(),
|
|
1922
|
-
keyword: z2.string().optional(),
|
|
1923
|
-
storeId: z2.string().optional()
|
|
1924
|
-
}),
|
|
1925
|
-
execute: async (input) => {
|
|
1926
|
-
audit("dsers_my_products", "dsers:product", input);
|
|
1927
|
-
const result = await withTimeout(getMyProducts(dsers, input), 3e4, "My products");
|
|
1928
|
-
audit("dsers_my_products", "dsers:product", void 0, "success");
|
|
1929
|
-
return compactToolResult(result);
|
|
1930
|
-
}
|
|
1931
|
-
});
|
|
1932
1506
|
return tools;
|
|
1933
1507
|
}
|
|
1934
1508
|
};
|
|
1935
1509
|
|
|
1936
|
-
// src/gateway/gateway.ts
|
|
1937
|
-
import { MemoryJobStore as MemoryJobStore2 } from "@lofder/dsers-mcp-product/dist/job-store-memory.js";
|
|
1938
|
-
|
|
1939
1510
|
// src/web/server.ts
|
|
1940
|
-
init_logger();
|
|
1941
1511
|
import { createServer } from "http";
|
|
1942
|
-
import { readFileSync as
|
|
1512
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, existsSync as existsSync6, statSync, readdirSync } from "fs";
|
|
1943
1513
|
import { join as join7, dirname as dirname2, extname } from "path";
|
|
1944
1514
|
import { fileURLToPath } from "url";
|
|
1945
1515
|
import { homedir as homedir3 } from "os";
|
|
@@ -1968,7 +1538,7 @@ function findWebDir() {
|
|
|
1968
1538
|
join7(thisDir, "..", "..", "dist", "web")
|
|
1969
1539
|
];
|
|
1970
1540
|
for (const d of candidates) {
|
|
1971
|
-
if (
|
|
1541
|
+
if (existsSync6(join7(d, "index.html"))) return d;
|
|
1972
1542
|
}
|
|
1973
1543
|
throw new Error(
|
|
1974
1544
|
`Web build not found (index.html). Looked in:
|
|
@@ -2007,7 +1577,7 @@ var WebChatServer = class _WebChatServer {
|
|
|
2007
1577
|
}
|
|
2008
1578
|
tryListen(tryPort, webDir) {
|
|
2009
1579
|
return new Promise((resolve) => {
|
|
2010
|
-
const indexHtml =
|
|
1580
|
+
const indexHtml = readFileSync4(join7(webDir, "index.html"), "utf-8");
|
|
2011
1581
|
const srv = createServer((req, res) => {
|
|
2012
1582
|
if (req.url === "/health") {
|
|
2013
1583
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
@@ -2020,11 +1590,11 @@ var WebChatServer = class _WebChatServer {
|
|
|
2020
1590
|
const fileName = urlPath.slice("/uploads/".length);
|
|
2021
1591
|
if (/^[a-zA-Z0-9._-]+$/.test(fileName)) {
|
|
2022
1592
|
const filePath2 = join7(uploadsDir, fileName);
|
|
2023
|
-
if (
|
|
1593
|
+
if (existsSync6(filePath2) && statSync(filePath2).isFile()) {
|
|
2024
1594
|
const ext = extname(filePath2);
|
|
2025
1595
|
const mime = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
2026
1596
|
res.writeHead(200, { "Content-Type": mime, "Cache-Control": "public, max-age=86400" });
|
|
2027
|
-
res.end(
|
|
1597
|
+
res.end(readFileSync4(filePath2));
|
|
2028
1598
|
return;
|
|
2029
1599
|
}
|
|
2030
1600
|
}
|
|
@@ -2034,12 +1604,12 @@ var WebChatServer = class _WebChatServer {
|
|
|
2034
1604
|
}
|
|
2035
1605
|
if (urlPath !== "/" && urlPath !== "/index.html") {
|
|
2036
1606
|
const filePath2 = join7(webDir, urlPath);
|
|
2037
|
-
if (
|
|
1607
|
+
if (existsSync6(filePath2) && statSync(filePath2).isFile()) {
|
|
2038
1608
|
const ext = extname(filePath2);
|
|
2039
1609
|
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
2040
1610
|
const cacheHeader = urlPath.includes("/assets/") ? "public, max-age=31536000, immutable" : "no-cache";
|
|
2041
1611
|
res.writeHead(200, { "Content-Type": contentType, "Cache-Control": cacheHeader });
|
|
2042
|
-
res.end(
|
|
1612
|
+
res.end(readFileSync4(filePath2));
|
|
2043
1613
|
return;
|
|
2044
1614
|
}
|
|
2045
1615
|
}
|
|
@@ -2253,7 +1823,7 @@ var WebChatServer = class _WebChatServer {
|
|
|
2253
1823
|
// ─── File Browsing ───────────────────────────────────────────
|
|
2254
1824
|
buildFileTree() {
|
|
2255
1825
|
const configDir = join7(homedir3(), ".dsclaw");
|
|
2256
|
-
if (!
|
|
1826
|
+
if (!existsSync6(configDir)) return [];
|
|
2257
1827
|
const scanDir = (dirPath, depth = 0) => {
|
|
2258
1828
|
if (depth > 5) return [];
|
|
2259
1829
|
try {
|
|
@@ -2301,7 +1871,7 @@ var WebChatServer = class _WebChatServer {
|
|
|
2301
1871
|
return;
|
|
2302
1872
|
}
|
|
2303
1873
|
try {
|
|
2304
|
-
if (!
|
|
1874
|
+
if (!existsSync6(filePath2) || !statSync(filePath2).isFile()) {
|
|
2305
1875
|
this.wsSend(ws, { type: "file_content", path: filePath2, content: "[\u6587\u4EF6\u4E0D\u5B58\u5728]" });
|
|
2306
1876
|
return;
|
|
2307
1877
|
}
|
|
@@ -2312,13 +1882,13 @@ var WebChatServer = class _WebChatServer {
|
|
|
2312
1882
|
}
|
|
2313
1883
|
const ext = extname(filePath2).toLowerCase();
|
|
2314
1884
|
if (_WebChatServer.IMAGE_EXTS.has(ext)) {
|
|
2315
|
-
const buf =
|
|
1885
|
+
const buf = readFileSync4(filePath2);
|
|
2316
1886
|
const mime = _WebChatServer.IMAGE_MIME[ext] ?? "application/octet-stream";
|
|
2317
1887
|
const dataUrl = `data:${mime};base64,${buf.toString("base64")}`;
|
|
2318
1888
|
this.wsSend(ws, { type: "file_content", path: filePath2, content: dataUrl, isImage: true });
|
|
2319
1889
|
return;
|
|
2320
1890
|
}
|
|
2321
|
-
const content =
|
|
1891
|
+
const content = readFileSync4(filePath2, "utf-8");
|
|
2322
1892
|
this.wsSend(ws, { type: "file_content", path: filePath2, content });
|
|
2323
1893
|
} catch (err) {
|
|
2324
1894
|
const reason = err instanceof Error ? err.message : "unknown";
|
|
@@ -2342,7 +1912,6 @@ var WebChatServer = class _WebChatServer {
|
|
|
2342
1912
|
};
|
|
2343
1913
|
|
|
2344
1914
|
// src/channels/telegram.ts
|
|
2345
|
-
init_logger();
|
|
2346
1915
|
import { Bot, InlineKeyboard } from "grammy";
|
|
2347
1916
|
import { nanoid as nanoid3 } from "nanoid";
|
|
2348
1917
|
var log9 = createLogger("channel:telegram");
|
|
@@ -2468,23 +2037,22 @@ ${card.summary}`;
|
|
|
2468
2037
|
|
|
2469
2038
|
// src/memory/file-provider.ts
|
|
2470
2039
|
import {
|
|
2471
|
-
readFileSync as
|
|
2040
|
+
readFileSync as readFileSync5,
|
|
2472
2041
|
writeFileSync as writeFileSync6,
|
|
2473
2042
|
appendFileSync as appendFileSync2,
|
|
2474
|
-
existsSync as
|
|
2043
|
+
existsSync as existsSync7,
|
|
2475
2044
|
mkdirSync as mkdirSync7,
|
|
2476
2045
|
readdirSync as readdirSync2
|
|
2477
2046
|
} from "fs";
|
|
2478
2047
|
import { join as join8 } from "path";
|
|
2479
2048
|
import { randomUUID } from "crypto";
|
|
2480
|
-
init_logger();
|
|
2481
2049
|
var log10 = createLogger("memory:file");
|
|
2482
2050
|
var MEMORY_DIR = join8(CONFIG_DIR, "memories");
|
|
2483
2051
|
var FileMemoryProvider = class {
|
|
2484
2052
|
name = "file";
|
|
2485
2053
|
degraded = false;
|
|
2486
2054
|
constructor() {
|
|
2487
|
-
if (!
|
|
2055
|
+
if (!existsSync7(MEMORY_DIR)) {
|
|
2488
2056
|
mkdirSync7(MEMORY_DIR, { recursive: true });
|
|
2489
2057
|
}
|
|
2490
2058
|
}
|
|
@@ -2520,8 +2088,8 @@ var FileMemoryProvider = class {
|
|
|
2520
2088
|
const files = filters.userId ? [this.userFile(filters.userId)] : readdirSync2(MEMORY_DIR).filter((f) => f.endsWith(".jsonl")).map((f) => join8(MEMORY_DIR, f));
|
|
2521
2089
|
const results = [];
|
|
2522
2090
|
for (const file of files) {
|
|
2523
|
-
if (!
|
|
2524
|
-
const lines =
|
|
2091
|
+
if (!existsSync7(file)) continue;
|
|
2092
|
+
const lines = readFileSync5(file, "utf-8").split("\n").filter((l) => l.trim());
|
|
2525
2093
|
for (const line of lines) {
|
|
2526
2094
|
try {
|
|
2527
2095
|
const stored = JSON.parse(line);
|
|
@@ -2544,8 +2112,8 @@ var FileMemoryProvider = class {
|
|
|
2544
2112
|
async update(id, content) {
|
|
2545
2113
|
const files = readdirSync2(MEMORY_DIR).filter((f) => f.endsWith(".jsonl")).map((f) => join8(MEMORY_DIR, f));
|
|
2546
2114
|
for (const file of files) {
|
|
2547
|
-
if (!
|
|
2548
|
-
const lines =
|
|
2115
|
+
if (!existsSync7(file)) continue;
|
|
2116
|
+
const lines = readFileSync5(file, "utf-8").split("\n").filter((l) => l.trim());
|
|
2549
2117
|
const updated = lines.map((line) => {
|
|
2550
2118
|
try {
|
|
2551
2119
|
const stored = JSON.parse(line);
|
|
@@ -2564,8 +2132,8 @@ var FileMemoryProvider = class {
|
|
|
2564
2132
|
async delete(id) {
|
|
2565
2133
|
const files = readdirSync2(MEMORY_DIR).filter((f) => f.endsWith(".jsonl")).map((f) => join8(MEMORY_DIR, f));
|
|
2566
2134
|
for (const file of files) {
|
|
2567
|
-
if (!
|
|
2568
|
-
const lines =
|
|
2135
|
+
if (!existsSync7(file)) continue;
|
|
2136
|
+
const lines = readFileSync5(file, "utf-8").split("\n").filter((l) => l.trim());
|
|
2569
2137
|
const filtered = lines.filter((line) => {
|
|
2570
2138
|
try {
|
|
2571
2139
|
const stored = JSON.parse(line);
|
|
@@ -2583,12 +2151,7 @@ var FileMemoryProvider = class {
|
|
|
2583
2151
|
}
|
|
2584
2152
|
};
|
|
2585
2153
|
|
|
2586
|
-
// src/gateway/gateway.ts
|
|
2587
|
-
init_logger();
|
|
2588
|
-
init_tracer();
|
|
2589
|
-
|
|
2590
2154
|
// src/resilience/degradation.ts
|
|
2591
|
-
init_logger();
|
|
2592
2155
|
var log11 = createLogger("degradation");
|
|
2593
2156
|
var states = /* @__PURE__ */ new Map();
|
|
2594
2157
|
function isDegraded(service) {
|
|
@@ -2606,7 +2169,6 @@ function degradationMessage(service) {
|
|
|
2606
2169
|
}
|
|
2607
2170
|
|
|
2608
2171
|
// src/context/token-counter.ts
|
|
2609
|
-
init_logger();
|
|
2610
2172
|
var log12 = createLogger("context:token");
|
|
2611
2173
|
var encode = null;
|
|
2612
2174
|
function countTokens(text) {
|
|
@@ -2618,7 +2180,6 @@ function countTokens(text) {
|
|
|
2618
2180
|
}
|
|
2619
2181
|
|
|
2620
2182
|
// src/context/context-budget.ts
|
|
2621
|
-
init_logger();
|
|
2622
2183
|
var log13 = createLogger("context:budget");
|
|
2623
2184
|
var FALLBACK_MAX_MESSAGES = 20;
|
|
2624
2185
|
var DEFAULT_BUDGET = {
|
|
@@ -2677,7 +2238,6 @@ function allocateBudget(systemPrompt, memories, history, config = {}) {
|
|
|
2677
2238
|
}
|
|
2678
2239
|
|
|
2679
2240
|
// src/context/compaction.ts
|
|
2680
|
-
init_logger();
|
|
2681
2241
|
var log14 = createLogger("context:compact");
|
|
2682
2242
|
async function compactWithFlush(allMessages, keepCount, memory, userId) {
|
|
2683
2243
|
if (allMessages.length <= keepCount) {
|
|
@@ -2766,49 +2326,48 @@ function extractKeyFacts(messages) {
|
|
|
2766
2326
|
|
|
2767
2327
|
// src/context/history-store.ts
|
|
2768
2328
|
import {
|
|
2769
|
-
readFileSync as
|
|
2329
|
+
readFileSync as readFileSync6,
|
|
2770
2330
|
writeFileSync as writeFileSync7,
|
|
2771
|
-
existsSync as
|
|
2331
|
+
existsSync as existsSync8,
|
|
2772
2332
|
mkdirSync as mkdirSync8,
|
|
2773
|
-
renameSync
|
|
2333
|
+
renameSync
|
|
2774
2334
|
} from "fs";
|
|
2775
2335
|
import { join as join9 } from "path";
|
|
2776
2336
|
import {
|
|
2777
|
-
randomBytes as
|
|
2778
|
-
createCipheriv as
|
|
2337
|
+
randomBytes as randomBytes3,
|
|
2338
|
+
createCipheriv as createCipheriv3,
|
|
2779
2339
|
createDecipheriv as createDecipheriv2,
|
|
2780
|
-
createHash as
|
|
2340
|
+
createHash as createHash4
|
|
2781
2341
|
} from "crypto";
|
|
2782
|
-
import { hostname as
|
|
2783
|
-
init_logger();
|
|
2342
|
+
import { hostname as hostname3, userInfo as userInfo3 } from "os";
|
|
2784
2343
|
var log15 = createLogger("context:history");
|
|
2785
2344
|
var HISTORY_DIR = join9(CONFIG_DIR, "history");
|
|
2786
2345
|
var MAX_LINES = 200;
|
|
2787
2346
|
var KEEP_AFTER_ROTATE = 100;
|
|
2788
|
-
var
|
|
2789
|
-
var
|
|
2790
|
-
var
|
|
2347
|
+
var ALG3 = "aes-256-gcm";
|
|
2348
|
+
var IV_LEN3 = 12;
|
|
2349
|
+
var TAG_LEN3 = 16;
|
|
2791
2350
|
var KEY_SEED2 = "dsclaw-history-v1";
|
|
2792
|
-
function
|
|
2351
|
+
function deriveKey3() {
|
|
2793
2352
|
let user = "";
|
|
2794
2353
|
try {
|
|
2795
|
-
user =
|
|
2354
|
+
user = userInfo3().username;
|
|
2796
2355
|
} catch {
|
|
2797
2356
|
user = process.env["USER"] ?? process.env["USERNAME"] ?? "default";
|
|
2798
2357
|
}
|
|
2799
|
-
return
|
|
2358
|
+
return createHash4("sha256").update(`${KEY_SEED2}:${hostname3()}:${user}`).digest();
|
|
2800
2359
|
}
|
|
2801
2360
|
function encryptStr(data) {
|
|
2802
|
-
const key =
|
|
2803
|
-
const iv =
|
|
2804
|
-
const cipher =
|
|
2361
|
+
const key = deriveKey3();
|
|
2362
|
+
const iv = randomBytes3(IV_LEN3);
|
|
2363
|
+
const cipher = createCipheriv3(ALG3, key, iv, { authTagLength: TAG_LEN3 });
|
|
2805
2364
|
const ct = Buffer.concat([cipher.update(data, "utf8"), cipher.final()]);
|
|
2806
2365
|
const tag = cipher.getAuthTag();
|
|
2807
2366
|
return Buffer.concat([iv, tag, ct]).toString("base64");
|
|
2808
2367
|
}
|
|
2809
2368
|
var cache = /* @__PURE__ */ new Map();
|
|
2810
2369
|
function ensureDir() {
|
|
2811
|
-
if (!
|
|
2370
|
+
if (!existsSync8(HISTORY_DIR)) {
|
|
2812
2371
|
mkdirSync8(HISTORY_DIR, { recursive: true });
|
|
2813
2372
|
}
|
|
2814
2373
|
}
|
|
@@ -2821,7 +2380,7 @@ function writeEncryptedFile(fp, messages) {
|
|
|
2821
2380
|
const encrypted = encryptStr(json);
|
|
2822
2381
|
const tmp = fp + ".tmp";
|
|
2823
2382
|
writeFileSync7(tmp, encrypted);
|
|
2824
|
-
|
|
2383
|
+
renameSync(tmp, fp);
|
|
2825
2384
|
}
|
|
2826
2385
|
function appendHistory(sessionKey, ...messages) {
|
|
2827
2386
|
const history = cache.get(sessionKey) ?? [];
|
|
@@ -3079,7 +2638,6 @@ var DSClawGateway = class {
|
|
|
3079
2638
|
webChannel = null;
|
|
3080
2639
|
memory;
|
|
3081
2640
|
agents = /* @__PURE__ */ new Map();
|
|
3082
|
-
jobStores = /* @__PURE__ */ new Map();
|
|
3083
2641
|
dsersClients = /* @__PURE__ */ new Map();
|
|
3084
2642
|
lastUserMessages = /* @__PURE__ */ new Map();
|
|
3085
2643
|
running = false;
|
|
@@ -3306,6 +2864,8 @@ var DSClawGateway = class {
|
|
|
3306
2864
|
session.dspiSessionState = void 0;
|
|
3307
2865
|
session.state = "new";
|
|
3308
2866
|
saveSession(userId, session);
|
|
2867
|
+
const agent = this.agents.get(userId);
|
|
2868
|
+
if (agent) await agent.destroy();
|
|
3309
2869
|
this.agents.delete(userId);
|
|
3310
2870
|
this.pushSettingsState(channel, userId, session);
|
|
3311
2871
|
break;
|
|
@@ -3558,8 +3118,9 @@ var DSClawGateway = class {
|
|
|
3558
3118
|
return;
|
|
3559
3119
|
}
|
|
3560
3120
|
if (cmd === "/logout") {
|
|
3121
|
+
const agent2 = this.agents.get(userId);
|
|
3122
|
+
if (agent2) await agent2.destroy();
|
|
3561
3123
|
this.agents.delete(userId);
|
|
3562
|
-
this.jobStores.delete(userId);
|
|
3563
3124
|
this.dsersClients.delete(userId);
|
|
3564
3125
|
this.lastUserMessages.delete(userId);
|
|
3565
3126
|
for (const key of sessionHistories.keys()) {
|
|
@@ -3599,7 +3160,7 @@ var DSClawGateway = class {
|
|
|
3599
3160
|
}
|
|
3600
3161
|
return;
|
|
3601
3162
|
}
|
|
3602
|
-
const agent = this.getOrCreateAgent(userId, session);
|
|
3163
|
+
const agent = await this.getOrCreateAgent(userId, session);
|
|
3603
3164
|
const sessionKey = `${channel.name}:${userId}`;
|
|
3604
3165
|
touchSession(sessionKey);
|
|
3605
3166
|
const history = sessionHistories.get(sessionKey) ?? [];
|
|
@@ -3848,7 +3409,7 @@ var DSClawGateway = class {
|
|
|
3848
3409
|
result: "success"
|
|
3849
3410
|
});
|
|
3850
3411
|
}
|
|
3851
|
-
getOrCreateAgent(userId, session) {
|
|
3412
|
+
async getOrCreateAgent(userId, session) {
|
|
3852
3413
|
let agent = this.agents.get(userId);
|
|
3853
3414
|
if (agent) return agent;
|
|
3854
3415
|
const dsersConfig = createDSersConfig(session.dspiEmail ?? "unknown");
|
|
@@ -3866,12 +3427,10 @@ var DSClawGateway = class {
|
|
|
3866
3427
|
};
|
|
3867
3428
|
const dsersSession = session.dspiSessionId ? { sessionId: session.dspiSessionId, state: session.dspiSessionState ?? "" } : void 0;
|
|
3868
3429
|
const authCb = () => this.triggerAuth(userId);
|
|
3869
|
-
|
|
3870
|
-
if (
|
|
3871
|
-
|
|
3872
|
-
this.jobStores.set(userId, jobStore);
|
|
3430
|
+
agent = new DSClawCoreAgent(dsersClient, this.memory, llm, authCb);
|
|
3431
|
+
if (dsersSession) {
|
|
3432
|
+
await agent.initMCP(dsersSession);
|
|
3873
3433
|
}
|
|
3874
|
-
agent = new DSClawCoreAgent(dsersClient, this.memory, llm, dsersSession, authCb, jobStore);
|
|
3875
3434
|
this.agents.set(userId, agent);
|
|
3876
3435
|
if (!agent.mcpAvailable) {
|
|
3877
3436
|
log16.warn({ userId }, "MCP unavailable for agent \u2014 notifying user");
|
|
@@ -3901,7 +3460,7 @@ var DSClawGateway = class {
|
|
|
3901
3460
|
};
|
|
3902
3461
|
|
|
3903
3462
|
// src/cli/pid.ts
|
|
3904
|
-
import { readFileSync as
|
|
3463
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync8, unlinkSync as unlinkSync2, existsSync as existsSync9 } from "fs";
|
|
3905
3464
|
import { join as join10 } from "path";
|
|
3906
3465
|
var PID_PATH = join10(CONFIG_DIR, "dsclaw.pid");
|
|
3907
3466
|
function writePid(port) {
|
|
@@ -3914,9 +3473,9 @@ function writePid(port) {
|
|
|
3914
3473
|
writeFileSync8(PID_PATH, JSON.stringify(info, null, 2), { mode: 384 });
|
|
3915
3474
|
}
|
|
3916
3475
|
function readPid() {
|
|
3917
|
-
if (!
|
|
3476
|
+
if (!existsSync9(PID_PATH)) return null;
|
|
3918
3477
|
try {
|
|
3919
|
-
const raw =
|
|
3478
|
+
const raw = readFileSync7(PID_PATH, "utf-8");
|
|
3920
3479
|
return JSON.parse(raw);
|
|
3921
3480
|
} catch {
|
|
3922
3481
|
return null;
|
|
@@ -3924,7 +3483,7 @@ function readPid() {
|
|
|
3924
3483
|
}
|
|
3925
3484
|
function removePid() {
|
|
3926
3485
|
try {
|
|
3927
|
-
if (
|
|
3486
|
+
if (existsSync9(PID_PATH)) unlinkSync2(PID_PATH);
|
|
3928
3487
|
} catch {
|
|
3929
3488
|
}
|
|
3930
3489
|
}
|
|
@@ -3991,7 +3550,6 @@ end tell`;
|
|
|
3991
3550
|
}
|
|
3992
3551
|
|
|
3993
3552
|
// src/cli/start.ts
|
|
3994
|
-
init_logger();
|
|
3995
3553
|
var log17 = createLogger("cli:start");
|
|
3996
3554
|
async function startCommand(opts) {
|
|
3997
3555
|
try {
|
|
@@ -4107,7 +3665,7 @@ function statusCommand() {
|
|
|
4107
3665
|
}
|
|
4108
3666
|
|
|
4109
3667
|
// src/cli/reset.ts
|
|
4110
|
-
import { existsSync as
|
|
3668
|
+
import { existsSync as existsSync10, readdirSync as readdirSync3, unlinkSync as unlinkSync3 } from "fs";
|
|
4111
3669
|
import { join as join11 } from "path";
|
|
4112
3670
|
function resetCommand(opts) {
|
|
4113
3671
|
const running = getRunningInstance();
|
|
@@ -4119,7 +3677,7 @@ function resetCommand(opts) {
|
|
|
4119
3677
|
}
|
|
4120
3678
|
const sessionsDir = join11(CONFIG_DIR, "sessions");
|
|
4121
3679
|
let cleared = 0;
|
|
4122
|
-
if (
|
|
3680
|
+
if (existsSync10(sessionsDir)) {
|
|
4123
3681
|
const files = readdirSync3(sessionsDir);
|
|
4124
3682
|
for (const f of files) {
|
|
4125
3683
|
try {
|
|
@@ -4132,7 +3690,7 @@ function resetCommand(opts) {
|
|
|
4132
3690
|
console.log(`
|
|
4133
3691
|
Cleared ${cleared} session(s).`);
|
|
4134
3692
|
if (opts.hard) {
|
|
4135
|
-
if (
|
|
3693
|
+
if (existsSync10(CONFIG_PATH)) {
|
|
4136
3694
|
try {
|
|
4137
3695
|
unlinkSync3(CONFIG_PATH);
|
|
4138
3696
|
console.log(" Deleted config file.");
|
|
@@ -4141,7 +3699,7 @@ function resetCommand(opts) {
|
|
|
4141
3699
|
}
|
|
4142
3700
|
}
|
|
4143
3701
|
const pidPath = join11(CONFIG_DIR, "dsclaw.pid");
|
|
4144
|
-
if (
|
|
3702
|
+
if (existsSync10(pidPath)) {
|
|
4145
3703
|
try {
|
|
4146
3704
|
unlinkSync3(pidPath);
|
|
4147
3705
|
} catch {
|