dsclaw 0.1.7 → 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 +160 -602
- 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 +3 -2
- package/dist/pkg/index.cjs +0 -101029
- package/dist/pkg/package.json +0 -11
- package/dist/pkg/release/dsclaw-macos-arm64 +0 -0
- package/dist/pkg/release/dsclaw-win-x64.exe +0 -0
- package/dist/pkg/web/assets/index--NY6w2i6.js +0 -40
- package/dist/pkg/web/assets/index-DXkqRQn1.css +0 -2
- package/dist/pkg/web/favicon.svg +0 -1
- package/dist/pkg/web/icon-192.png +0 -0
- package/dist/pkg/web/icon-512.png +0 -0
- package/dist/pkg/web/icons.svg +0 -24
- package/dist/pkg/web/index.html +0 -20
- package/dist/pkg/web/manifest.json +0 -28
- package/dist/pkg/web/sw.js +0 -37
package/dist/index.js
CHANGED
|
@@ -1,56 +1,3 @@
|
|
|
1
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
2
|
-
var __esm = (fn, res) => function __init() {
|
|
3
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
// src/shared/tracer.ts
|
|
7
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
8
|
-
import { nanoid } from "nanoid";
|
|
9
|
-
function runWithTrace(ctx, fn) {
|
|
10
|
-
const traceId = ctx.traceId ?? nanoid(12);
|
|
11
|
-
return storage.run({ traceId, ...ctx }, fn);
|
|
12
|
-
}
|
|
13
|
-
function getTraceContext() {
|
|
14
|
-
return storage.getStore() ?? { traceId: nanoid(12) };
|
|
15
|
-
}
|
|
16
|
-
function getTraceId() {
|
|
17
|
-
return getTraceContext().traceId;
|
|
18
|
-
}
|
|
19
|
-
var storage;
|
|
20
|
-
var init_tracer = __esm({
|
|
21
|
-
"src/shared/tracer.ts"() {
|
|
22
|
-
"use strict";
|
|
23
|
-
storage = new AsyncLocalStorage();
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
// src/shared/logger.ts
|
|
28
|
-
import pino from "pino";
|
|
29
|
-
function createLogger(module) {
|
|
30
|
-
return rootLogger.child({ module });
|
|
31
|
-
}
|
|
32
|
-
var isPkg, rootLogger;
|
|
33
|
-
var init_logger = __esm({
|
|
34
|
-
"src/shared/logger.ts"() {
|
|
35
|
-
"use strict";
|
|
36
|
-
init_tracer();
|
|
37
|
-
isPkg = typeof process.pkg !== "undefined";
|
|
38
|
-
rootLogger = pino({
|
|
39
|
-
level: process.env["LOG_LEVEL"] ?? "info",
|
|
40
|
-
transport: !isPkg && process.env["NODE_ENV"] !== "production" ? { target: "pino-pretty", options: { colorize: true } } : void 0,
|
|
41
|
-
mixin() {
|
|
42
|
-
const ctx = getTraceContext();
|
|
43
|
-
return {
|
|
44
|
-
traceId: ctx.traceId,
|
|
45
|
-
...ctx.userId ? { userId: ctx.userId } : {},
|
|
46
|
-
...ctx.channelId ? { channelId: ctx.channelId } : {},
|
|
47
|
-
...ctx.agentId ? { agentId: ctx.agentId } : {}
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
|
|
54
1
|
// src/shared/errors.ts
|
|
55
2
|
var ErrorCategory = {
|
|
56
3
|
RETRYABLE: "RETRYABLE",
|
|
@@ -92,9 +39,42 @@ function classifyHttpError(status, body) {
|
|
|
92
39
|
return ErrorCategory.FATAL;
|
|
93
40
|
}
|
|
94
41
|
|
|
95
|
-
// src/
|
|
96
|
-
|
|
97
|
-
|
|
42
|
+
// src/shared/logger.ts
|
|
43
|
+
import pino from "pino";
|
|
44
|
+
|
|
45
|
+
// src/shared/tracer.ts
|
|
46
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
47
|
+
import { nanoid } from "nanoid";
|
|
48
|
+
var storage = new AsyncLocalStorage();
|
|
49
|
+
function runWithTrace(ctx, fn) {
|
|
50
|
+
const traceId = ctx.traceId ?? nanoid(12);
|
|
51
|
+
return storage.run({ traceId, ...ctx }, fn);
|
|
52
|
+
}
|
|
53
|
+
function getTraceContext() {
|
|
54
|
+
return storage.getStore() ?? { traceId: nanoid(12) };
|
|
55
|
+
}
|
|
56
|
+
function getTraceId() {
|
|
57
|
+
return getTraceContext().traceId;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/shared/logger.ts
|
|
61
|
+
var isPkg = typeof process.pkg !== "undefined";
|
|
62
|
+
var rootLogger = pino({
|
|
63
|
+
level: process.env["LOG_LEVEL"] ?? "info",
|
|
64
|
+
transport: !isPkg && process.env["NODE_ENV"] !== "production" ? { target: "pino-pretty", options: { colorize: true } } : void 0,
|
|
65
|
+
mixin() {
|
|
66
|
+
const ctx = getTraceContext();
|
|
67
|
+
return {
|
|
68
|
+
traceId: ctx.traceId,
|
|
69
|
+
...ctx.userId ? { userId: ctx.userId } : {},
|
|
70
|
+
...ctx.channelId ? { channelId: ctx.channelId } : {},
|
|
71
|
+
...ctx.agentId ? { agentId: ctx.agentId } : {}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
function createLogger(module) {
|
|
76
|
+
return rootLogger.child({ module });
|
|
77
|
+
}
|
|
98
78
|
|
|
99
79
|
// src/shared/utils.ts
|
|
100
80
|
import { createHash } from "crypto";
|
|
@@ -120,7 +100,6 @@ import { appendFileSync, mkdirSync as mkdirSync2, existsSync as existsSync2 } fr
|
|
|
120
100
|
import { join as join2 } from "path";
|
|
121
101
|
|
|
122
102
|
// src/gateway/config.ts
|
|
123
|
-
init_logger();
|
|
124
103
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
125
104
|
import { join } from "path";
|
|
126
105
|
import { homedir } from "os";
|
|
@@ -161,7 +140,6 @@ function saveConfig(config) {
|
|
|
161
140
|
}
|
|
162
141
|
|
|
163
142
|
// src/shared/audit.ts
|
|
164
|
-
init_tracer();
|
|
165
143
|
var AUDIT_DIR = join2(CONFIG_DIR, "audit");
|
|
166
144
|
function ensureAuditDir() {
|
|
167
145
|
if (!existsSync2(AUDIT_DIR)) {
|
|
@@ -202,7 +180,6 @@ import {
|
|
|
202
180
|
} from "fs";
|
|
203
181
|
import { join as join3 } from "path";
|
|
204
182
|
import { hostname, userInfo } from "os";
|
|
205
|
-
init_logger();
|
|
206
183
|
var log2 = createLogger("user-session");
|
|
207
184
|
var ALG = "aes-256-gcm";
|
|
208
185
|
var IV_LEN = 12;
|
|
@@ -325,7 +302,6 @@ function isOnboarding(state) {
|
|
|
325
302
|
}
|
|
326
303
|
|
|
327
304
|
// src/dsers/auth.ts
|
|
328
|
-
init_logger();
|
|
329
305
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4, existsSync as existsSync4, chmodSync } from "fs";
|
|
330
306
|
import { dirname } from "path";
|
|
331
307
|
var log3 = createLogger("dsers:auth");
|
|
@@ -494,12 +470,7 @@ var DSersAuth = class {
|
|
|
494
470
|
}
|
|
495
471
|
};
|
|
496
472
|
|
|
497
|
-
// src/dsers/client.ts
|
|
498
|
-
init_logger();
|
|
499
|
-
init_tracer();
|
|
500
|
-
|
|
501
473
|
// src/resilience/rate-limiter.ts
|
|
502
|
-
init_logger();
|
|
503
474
|
import pLimit from "p-limit";
|
|
504
475
|
var log4 = createLogger("rate-limiter");
|
|
505
476
|
var DEFAULT_OUTBOUND_LIMITS = {
|
|
@@ -781,7 +752,6 @@ function findAllChromium() {
|
|
|
781
752
|
}
|
|
782
753
|
|
|
783
754
|
// src/dsers/browser-auth.ts
|
|
784
|
-
init_logger();
|
|
785
755
|
var log6 = createLogger("dsers:browser-auth");
|
|
786
756
|
var DSERS_LOGIN_URL = "https://accounts.dsers.com/accounts/login";
|
|
787
757
|
var LOGIN_API_PATTERN = "/account-user-bff/v1/users/login";
|
|
@@ -1009,68 +979,61 @@ async function loginViaCDP() {
|
|
|
1009
979
|
}
|
|
1010
980
|
|
|
1011
981
|
// src/agents/core-agent.ts
|
|
1012
|
-
init_logger();
|
|
1013
982
|
import { generateText, streamText, tool as aiTool, stepCountIs } from "ai";
|
|
1014
983
|
import { createOpenAI } from "@ai-sdk/openai";
|
|
1015
984
|
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
1016
985
|
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
|
1017
986
|
import { z as z2 } from "zod";
|
|
987
|
+
import { createMCPClient } from "@ai-sdk/mcp";
|
|
988
|
+
import { Experimental_StdioMCPTransport as StdioTransport } from "@ai-sdk/mcp/mcp-stdio";
|
|
1018
989
|
|
|
1019
|
-
// src/dsers/
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
return client.get("/dsers-product-bff/import-list", filtered);
|
|
1023
|
-
}
|
|
1024
|
-
async function getImportListItem(client, id) {
|
|
1025
|
-
return client.get(`/dsers-product-bff/import-list/${id}`);
|
|
1026
|
-
}
|
|
1027
|
-
async function getMyProducts(client, params) {
|
|
1028
|
-
return client.get("/dsers-product-bff/my-product", params);
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
// src/agents/core-agent.ts
|
|
1032
|
-
import { configFromToken } from "@lofder/dsers-mcp-product/dist/dsers/config.js";
|
|
1033
|
-
import { buildProvider } from "@lofder/dsers-mcp-product/dist/provider.js";
|
|
1034
|
-
import { ImportFlowService } from "@lofder/dsers-mcp-product/dist/service.js";
|
|
1035
|
-
import { MemoryJobStore } from "@lofder/dsers-mcp-product/dist/job-store-memory.js";
|
|
1036
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, renameSync, existsSync as existsSync6 } from "fs";
|
|
990
|
+
// src/dsers/mcp-token-bridge.ts
|
|
991
|
+
import { randomBytes as randomBytes2, createCipheriv as createCipheriv2, createHash as createHash3 } from "crypto";
|
|
992
|
+
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, chmodSync as chmodSync2 } from "fs";
|
|
1037
993
|
import { join as join6 } from "path";
|
|
1038
|
-
import { homedir as homedir2 } from "os";
|
|
1039
|
-
var
|
|
1040
|
-
var
|
|
1041
|
-
var
|
|
1042
|
-
var
|
|
1043
|
-
|
|
1044
|
-
|
|
994
|
+
import { homedir as homedir2, hostname as hostname2, userInfo as userInfo2 } from "os";
|
|
995
|
+
var ALG2 = "aes-256-gcm";
|
|
996
|
+
var IV_LEN2 = 12;
|
|
997
|
+
var TAG_LEN2 = 16;
|
|
998
|
+
var PACKAGE_SEED = "dsers-mcp-product-v1";
|
|
999
|
+
var DSERS_BASE_URL = "https://bff-api-gw.dsers.com";
|
|
1000
|
+
function deriveKey2() {
|
|
1001
|
+
const host = hostname2();
|
|
1002
|
+
let user = "";
|
|
1045
1003
|
try {
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
for (const [short, entry] of Object.entries(raw)) {
|
|
1050
|
-
if (now - entry.ts < JOB_ID_TTL_MS) {
|
|
1051
|
-
map.set(short, entry.full);
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
} catch (err) {
|
|
1055
|
-
log7.warn({ err }, "Failed to load job ID map from disk");
|
|
1004
|
+
user = userInfo2().username;
|
|
1005
|
+
} catch {
|
|
1006
|
+
user = process.env["USER"] ?? process.env["USERNAME"] ?? "default";
|
|
1056
1007
|
}
|
|
1057
|
-
return
|
|
1008
|
+
return createHash3("sha256").update(`${PACKAGE_SEED}:${host}:${user}`).digest();
|
|
1009
|
+
}
|
|
1010
|
+
function encrypt2(data) {
|
|
1011
|
+
const key = deriveKey2();
|
|
1012
|
+
const iv = randomBytes2(IV_LEN2);
|
|
1013
|
+
const cipher = createCipheriv2(ALG2, key, iv, { authTagLength: TAG_LEN2 });
|
|
1014
|
+
const ct = Buffer.concat([cipher.update(data, "utf8"), cipher.final()]);
|
|
1015
|
+
const tag = cipher.getAuthTag();
|
|
1016
|
+
return Buffer.concat([iv, tag, ct]).toString("base64");
|
|
1058
1017
|
}
|
|
1059
|
-
function
|
|
1018
|
+
function writeMCPToken(sessionId, state) {
|
|
1019
|
+
const dir = join6(homedir2(), ".dsers-mcp");
|
|
1020
|
+
mkdirSync5(dir, { recursive: true, mode: 448 });
|
|
1021
|
+
const payload = JSON.stringify({
|
|
1022
|
+
session_id: sessionId,
|
|
1023
|
+
state,
|
|
1024
|
+
base_url: DSERS_BASE_URL,
|
|
1025
|
+
ts: Date.now()
|
|
1026
|
+
});
|
|
1027
|
+
const file = join6(dir, "credentials");
|
|
1028
|
+
writeFileSync4(file, encrypt2(payload), "utf-8");
|
|
1060
1029
|
try {
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
const now = Date.now();
|
|
1064
|
-
for (const [short, full] of map) {
|
|
1065
|
-
obj[short] = { full, ts: now };
|
|
1066
|
-
}
|
|
1067
|
-
const tmp = JOB_ID_MAP_FILE + ".tmp";
|
|
1068
|
-
writeFileSync4(tmp, JSON.stringify(obj));
|
|
1069
|
-
renameSync(tmp, JOB_ID_MAP_FILE);
|
|
1070
|
-
} catch (err) {
|
|
1071
|
-
log7.warn({ err }, "Failed to persist job ID map");
|
|
1030
|
+
chmodSync2(file, 384);
|
|
1031
|
+
} catch {
|
|
1072
1032
|
}
|
|
1073
1033
|
}
|
|
1034
|
+
|
|
1035
|
+
// src/agents/core-agent.ts
|
|
1036
|
+
var log7 = createLogger("agent:core");
|
|
1074
1037
|
function normalizeBaseUrl(url) {
|
|
1075
1038
|
if (!url) return void 0;
|
|
1076
1039
|
let u = url.trim();
|
|
@@ -1112,87 +1075,6 @@ function withToolAwareTimeout(promise, baseMs, isToolActive, label) {
|
|
|
1112
1075
|
});
|
|
1113
1076
|
return Promise.race([promise.finally(cleanup2), timeoutPromise]);
|
|
1114
1077
|
}
|
|
1115
|
-
var MCP_DROP_KEYS = /* @__PURE__ */ new Set([
|
|
1116
|
-
"image_urls",
|
|
1117
|
-
"description_html_snippet",
|
|
1118
|
-
"effective_rules_snapshot",
|
|
1119
|
-
"requested_rules",
|
|
1120
|
-
"original_draft",
|
|
1121
|
-
"resolved_source_url",
|
|
1122
|
-
"resolver_mode",
|
|
1123
|
-
"tags_before",
|
|
1124
|
-
"tags_after"
|
|
1125
|
-
]);
|
|
1126
|
-
function compactToolResult(result, jobIdMap) {
|
|
1127
|
-
if (!result || typeof result !== "object") return result;
|
|
1128
|
-
const isMcpPreview = "job_id" in result && ("title_after" in result || "status" in result);
|
|
1129
|
-
if (isMcpPreview) {
|
|
1130
|
-
const out2 = {};
|
|
1131
|
-
for (const [k, v] of Object.entries(result)) {
|
|
1132
|
-
if (MCP_DROP_KEYS.has(k)) continue;
|
|
1133
|
-
out2[k] = v;
|
|
1134
|
-
}
|
|
1135
|
-
if (out2.job_id && typeof out2.job_id === "string") {
|
|
1136
|
-
const dotIdx = out2.job_id.indexOf(".");
|
|
1137
|
-
if (dotIdx > 0) {
|
|
1138
|
-
const short = out2.job_id.slice(0, dotIdx);
|
|
1139
|
-
if (jobIdMap) jobIdMap.set(short, out2.job_id);
|
|
1140
|
-
out2.job_id = short;
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
|
-
if (out2.skus && Array.isArray(out2.skus) && out2.skus.length > 1) {
|
|
1144
|
-
const [header, ...rows] = out2.skus;
|
|
1145
|
-
out2.variant_summary = rows.slice(0, 5).map((row) => {
|
|
1146
|
-
const obj = {};
|
|
1147
|
-
header.forEach((key, i) => {
|
|
1148
|
-
obj[key] = row[i];
|
|
1149
|
-
});
|
|
1150
|
-
return obj;
|
|
1151
|
-
});
|
|
1152
|
-
if (rows.length > 5) {
|
|
1153
|
-
out2.variant_summary_note = `Showing 5 of ${rows.length} variants`;
|
|
1154
|
-
}
|
|
1155
|
-
delete out2.skus;
|
|
1156
|
-
}
|
|
1157
|
-
if (out2.title_after) {
|
|
1158
|
-
out2._title_modified = true;
|
|
1159
|
-
out2._display_title = out2.title_after;
|
|
1160
|
-
}
|
|
1161
|
-
if (out2.desc_changed) {
|
|
1162
|
-
out2._description_modified = true;
|
|
1163
|
-
}
|
|
1164
|
-
if (out2.warnings?.length > 5) out2.warnings = out2.warnings.slice(0, 5);
|
|
1165
|
-
if (out2.stores) {
|
|
1166
|
-
out2.stores = out2.stores.map((s) => ({
|
|
1167
|
-
store_ref: s.store_ref,
|
|
1168
|
-
display_name: s.display_name,
|
|
1169
|
-
platform: s.platform
|
|
1170
|
-
}));
|
|
1171
|
-
}
|
|
1172
|
-
if (out2.account_info) {
|
|
1173
|
-
const ai = out2.account_info;
|
|
1174
|
-
out2.account_info = { plan: ai.plan, limits: ai.limits, aliexpress_auth: ai.aliexpress_auth };
|
|
1175
|
-
}
|
|
1176
|
-
return out2;
|
|
1177
|
-
}
|
|
1178
|
-
const json = JSON.stringify(result);
|
|
1179
|
-
if (json.length <= 4e3) return result;
|
|
1180
|
-
const out = {};
|
|
1181
|
-
for (const [k, v] of Object.entries(result)) {
|
|
1182
|
-
if (MCP_DROP_KEYS.has(k)) continue;
|
|
1183
|
-
if (Array.isArray(v) && v.length > 20) {
|
|
1184
|
-
out[k] = v.slice(0, 20);
|
|
1185
|
-
out[`_${k}_truncated`] = `${v.length} total, showing first 20`;
|
|
1186
|
-
} else {
|
|
1187
|
-
out[k] = v;
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
const outJson = JSON.stringify(out);
|
|
1191
|
-
if (outJson.length > 8e3) {
|
|
1192
|
-
return { _truncated: true, _original_size: json.length, summary: outJson.slice(0, 6e3) + "..." };
|
|
1193
|
-
}
|
|
1194
|
-
return out;
|
|
1195
|
-
}
|
|
1196
1078
|
function buildModel(llm) {
|
|
1197
1079
|
const baseURL = normalizeBaseUrl(llm.baseUrl);
|
|
1198
1080
|
if (baseURL || llm.provider === "other") {
|
|
@@ -1307,42 +1189,41 @@ var DSClawCoreAgent = class {
|
|
|
1307
1189
|
memory;
|
|
1308
1190
|
llm;
|
|
1309
1191
|
customTools = /* @__PURE__ */ new Map();
|
|
1310
|
-
|
|
1311
|
-
jobIdMap = loadJobIdMap();
|
|
1192
|
+
mcpClient = null;
|
|
1312
1193
|
mcpAvailable = false;
|
|
1313
1194
|
authCallback;
|
|
1314
|
-
constructor(dsers, memory, llm,
|
|
1195
|
+
constructor(dsers, memory, llm, authCallback) {
|
|
1315
1196
|
this.dsers = dsers;
|
|
1316
1197
|
this.memory = memory;
|
|
1317
1198
|
this.llm = llm;
|
|
1318
1199
|
this.authCallback = authCallback;
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
}
|
|
1200
|
+
}
|
|
1201
|
+
async initMCP(dsersSession) {
|
|
1202
|
+
try {
|
|
1203
|
+
writeMCPToken(dsersSession.sessionId, dsersSession.state);
|
|
1204
|
+
this.mcpClient = await createMCPClient({
|
|
1205
|
+
transport: new StdioTransport({
|
|
1206
|
+
command: "npx",
|
|
1207
|
+
args: ["-y", "@lofder/dsers-mcp-product"]
|
|
1208
|
+
})
|
|
1209
|
+
});
|
|
1210
|
+
this.mcpAvailable = true;
|
|
1211
|
+
log7.info("MCP client connected via stdio");
|
|
1212
|
+
} catch (err) {
|
|
1213
|
+
log7.warn({ err }, "Failed to connect MCP \u2014 product tools unavailable");
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
async destroy() {
|
|
1217
|
+
if (this.mcpClient) {
|
|
1218
|
+
await this.mcpClient.close();
|
|
1219
|
+
this.mcpClient = null;
|
|
1220
|
+
this.mcpAvailable = false;
|
|
1329
1221
|
}
|
|
1330
1222
|
}
|
|
1331
1223
|
getSystemPrompt() {
|
|
1332
1224
|
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.";
|
|
1333
1225
|
return SYSTEM_PROMPT.replace("{{LANGUAGE_RULE}}", `Language rule: ${rule}`);
|
|
1334
1226
|
}
|
|
1335
|
-
shortenJobId(fullId) {
|
|
1336
|
-
const dotIdx = fullId.indexOf(".");
|
|
1337
|
-
if (dotIdx <= 0) return fullId;
|
|
1338
|
-
const short = fullId.slice(0, dotIdx);
|
|
1339
|
-
this.jobIdMap.set(short, fullId);
|
|
1340
|
-
persistJobIdMap(this.jobIdMap);
|
|
1341
|
-
return short;
|
|
1342
|
-
}
|
|
1343
|
-
resolveJobId(maybeShort) {
|
|
1344
|
-
return this.jobIdMap.get(maybeShort) ?? maybeShort;
|
|
1345
|
-
}
|
|
1346
1227
|
registerTool(t2) {
|
|
1347
1228
|
this.customTools.set(t2.name, t2);
|
|
1348
1229
|
}
|
|
@@ -1369,7 +1250,7 @@ ${memories.map((m2) => `- ${m2.content}`).join("\n")}` : "";
|
|
|
1369
1250
|
),
|
|
1370
1251
|
{ role: "user", content: message }
|
|
1371
1252
|
];
|
|
1372
|
-
const tools = this.buildAITools(context);
|
|
1253
|
+
const tools = await this.buildAITools(context);
|
|
1373
1254
|
log7.info(
|
|
1374
1255
|
{ userId: context.userId, messageLen: message.length, toolCount: Object.keys(tools).length },
|
|
1375
1256
|
"Processing message"
|
|
@@ -1421,9 +1302,9 @@ ${memories.map((m2) => `- ${m2.content}`).join("\n")}` : "";
|
|
|
1421
1302
|
processStream(message, context, onStatus, callbacks, attachments) {
|
|
1422
1303
|
const model = buildModel(this.llm);
|
|
1423
1304
|
const memories = this.memory.search(message, { userId: context.userId, limit: 5 }).catch(() => []);
|
|
1424
|
-
const
|
|
1305
|
+
const toolsPromise = this.buildAITools(context);
|
|
1425
1306
|
const self = this;
|
|
1426
|
-
const streamPromise = memories.then((mems) => {
|
|
1307
|
+
const streamPromise = Promise.all([memories, toolsPromise]).then(([mems, tools]) => {
|
|
1427
1308
|
const memoryContext = mems.length > 0 ? `
|
|
1428
1309
|
|
|
1429
1310
|
Relevant memories:
|
|
@@ -1559,207 +1440,13 @@ ${mems.map((m2) => `- ${m2.content}`).join("\n")}` : "";
|
|
|
1559
1440
|
});
|
|
1560
1441
|
return { textStream, response };
|
|
1561
1442
|
}
|
|
1562
|
-
buildAITools(
|
|
1563
|
-
const dsers = this.dsers;
|
|
1564
|
-
const svc = this.importService;
|
|
1565
|
-
const jmap = this.jobIdMap;
|
|
1566
|
-
const resolveJid = (id) => this.resolveJobId(id);
|
|
1567
|
-
const audit = (action, target, params, result = "pending") => writeAuditLog({ userId: context.userId, agentId: this.id, action, target, params, result });
|
|
1443
|
+
async buildAITools(_context) {
|
|
1568
1444
|
const tools = {};
|
|
1569
|
-
if (
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
inputSchema: z2.object({
|
|
1573
|
-
target_store: z2.string().optional().describe("Filter by store ID or name")
|
|
1574
|
-
}),
|
|
1575
|
-
execute: async (input) => {
|
|
1576
|
-
audit("dsers_store_discover", "mcp");
|
|
1577
|
-
const result = await withTimeout(svc.getRuleCapabilities(input), 6e4, "Store discovery");
|
|
1578
|
-
audit("dsers_store_discover", "mcp", void 0, "success");
|
|
1579
|
-
return compactToolResult(result, jmap);
|
|
1580
|
-
}
|
|
1581
|
-
});
|
|
1582
|
-
tools.dsers_product_import = aiTool({
|
|
1583
|
-
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.",
|
|
1584
|
-
inputSchema: z2.object({
|
|
1585
|
-
source_url: z2.string().optional().describe("Supplier product URL"),
|
|
1586
|
-
source_urls_json: z2.string().optional().describe("Batch: JSON array of URLs"),
|
|
1587
|
-
job_id: z2.string().optional().describe("Existing job ID for rule updates"),
|
|
1588
|
-
source_hint: z2.string().optional().describe("auto|aliexpress|alibaba|accio"),
|
|
1589
|
-
country: z2.string().default("US").describe("Country code"),
|
|
1590
|
-
target_store: z2.string().optional().describe("Store ID or name"),
|
|
1591
|
-
visibility_mode: z2.string().optional().describe("backend_only|sell_immediately"),
|
|
1592
|
-
rules_json: z2.string().optional().describe(
|
|
1593
|
-
"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?}]."
|
|
1594
|
-
)
|
|
1595
|
-
}),
|
|
1596
|
-
execute: async (input) => {
|
|
1597
|
-
audit("dsers_product_import", "mcp", input);
|
|
1598
|
-
const payload = {};
|
|
1599
|
-
if (input.job_id && !input.source_url && !input.source_urls_json) {
|
|
1600
|
-
payload.job_id = resolveJid(input.job_id);
|
|
1601
|
-
if (input.rules_json) {
|
|
1602
|
-
try {
|
|
1603
|
-
payload.rules = JSON.parse(input.rules_json);
|
|
1604
|
-
} catch {
|
|
1605
|
-
throw new Error(
|
|
1606
|
-
'Invalid JSON in rules_json. Expected: {"pricing":{"mode":"fixed_price","fixed_price":9.99}} or {"content":{"title_override":"New Title"}}'
|
|
1607
|
-
);
|
|
1608
|
-
}
|
|
1609
|
-
} else {
|
|
1610
|
-
payload._keep_existing_rules = true;
|
|
1611
|
-
}
|
|
1612
|
-
if (input.target_store) payload.target_store = input.target_store;
|
|
1613
|
-
if (input.visibility_mode) payload.visibility_mode = input.visibility_mode;
|
|
1614
|
-
const result2 = await withTimeout(svc.reapplyRules(payload), 12e4, "Rule reapply");
|
|
1615
|
-
audit("dsers_product_import", "mcp", void 0, "success");
|
|
1616
|
-
return compactToolResult(result2, jmap);
|
|
1617
|
-
}
|
|
1618
|
-
if (input.source_url) payload.source_url = input.source_url;
|
|
1619
|
-
if (input.source_urls_json) {
|
|
1620
|
-
try {
|
|
1621
|
-
payload.source_urls = JSON.parse(input.source_urls_json);
|
|
1622
|
-
} catch {
|
|
1623
|
-
throw new Error("Invalid JSON in source_urls_json.");
|
|
1624
|
-
}
|
|
1625
|
-
}
|
|
1626
|
-
if (input.source_hint) payload.source_hint = input.source_hint;
|
|
1627
|
-
if (input.country) payload.country = input.country;
|
|
1628
|
-
if (input.target_store) payload.target_store = input.target_store;
|
|
1629
|
-
payload.visibility_mode = input.visibility_mode || "backend_only";
|
|
1630
|
-
if (input.rules_json) {
|
|
1631
|
-
try {
|
|
1632
|
-
payload.rules = JSON.parse(input.rules_json);
|
|
1633
|
-
} catch {
|
|
1634
|
-
throw new Error("Invalid JSON in rules_json.");
|
|
1635
|
-
}
|
|
1636
|
-
}
|
|
1637
|
-
const result = await withTimeout(svc.prepareImportCandidate(payload), 12e4, "Product import");
|
|
1638
|
-
audit("dsers_product_import", "mcp", void 0, "success");
|
|
1639
|
-
return compactToolResult(result, jmap);
|
|
1640
|
-
}
|
|
1641
|
-
});
|
|
1642
|
-
tools.dsers_product_preview = aiTool({
|
|
1643
|
-
description: "Reload preview for an imported job. Returns prices, variants, images.",
|
|
1644
|
-
inputSchema: z2.object({
|
|
1645
|
-
job_id: z2.string().describe("Job ID from dsers_product_import"),
|
|
1646
|
-
variant_offset: z2.number().optional().describe("Start index for variant pagination"),
|
|
1647
|
-
variant_limit: z2.number().optional().describe("Max variants to return")
|
|
1648
|
-
}),
|
|
1649
|
-
execute: async (input) => {
|
|
1650
|
-
audit("dsers_product_preview", "mcp", input);
|
|
1651
|
-
const result = await withTimeout(svc.getImportPreview({ ...input, job_id: resolveJid(input.job_id) }), 3e4, "Preview");
|
|
1652
|
-
audit("dsers_product_preview", "mcp", void 0, "success");
|
|
1653
|
-
return compactToolResult(result, jmap);
|
|
1654
|
-
}
|
|
1655
|
-
});
|
|
1656
|
-
tools.dsers_store_push = aiTool({
|
|
1657
|
-
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.",
|
|
1658
|
-
inputSchema: z2.object({
|
|
1659
|
-
job_id: z2.string().optional().describe("Job ID"),
|
|
1660
|
-
job_ids_json: z2.string().optional().describe("Batch: JSON array of job IDs"),
|
|
1661
|
-
target_store: z2.string().optional().describe("Store ID or name from dsers_store_discover"),
|
|
1662
|
-
target_stores_json: z2.string().optional().describe("Multi-store: JSON array of store names"),
|
|
1663
|
-
visibility_mode: z2.string().optional().describe("backend_only|sell_immediately"),
|
|
1664
|
-
force_push: z2.boolean().optional().describe("Override safety checks"),
|
|
1665
|
-
push_options_json: z2.string().optional().describe(
|
|
1666
|
-
'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"}'
|
|
1667
|
-
)
|
|
1668
|
-
}),
|
|
1669
|
-
execute: async (input) => {
|
|
1670
|
-
audit("dsers_store_push", "mcp", input);
|
|
1671
|
-
const payload = {};
|
|
1672
|
-
if (input.job_ids_json) {
|
|
1673
|
-
try {
|
|
1674
|
-
const ids = JSON.parse(input.job_ids_json);
|
|
1675
|
-
payload.job_ids = ids.map(resolveJid);
|
|
1676
|
-
} catch {
|
|
1677
|
-
throw new Error("Invalid JSON in job_ids_json");
|
|
1678
|
-
}
|
|
1679
|
-
} else if (input.job_id) {
|
|
1680
|
-
payload.job_id = resolveJid(input.job_id);
|
|
1681
|
-
}
|
|
1682
|
-
if (input.target_store) payload.target_store = input.target_store;
|
|
1683
|
-
if (input.target_stores_json) {
|
|
1684
|
-
try {
|
|
1685
|
-
payload.target_stores = JSON.parse(input.target_stores_json);
|
|
1686
|
-
} catch {
|
|
1687
|
-
throw new Error("Invalid JSON in target_stores_json");
|
|
1688
|
-
}
|
|
1689
|
-
}
|
|
1690
|
-
if (input.visibility_mode) payload.visibility_mode = input.visibility_mode;
|
|
1691
|
-
if (input.force_push) payload.force_push = true;
|
|
1692
|
-
if (input.push_options_json) {
|
|
1693
|
-
try {
|
|
1694
|
-
payload.push_options = JSON.parse(input.push_options_json);
|
|
1695
|
-
} catch {
|
|
1696
|
-
throw new Error("Invalid JSON in push_options_json");
|
|
1697
|
-
}
|
|
1698
|
-
}
|
|
1699
|
-
const result = await withTimeout(svc.confirmPushToStore(payload), 18e4, "Push to store");
|
|
1700
|
-
audit("dsers_store_push", "mcp", void 0, "success");
|
|
1701
|
-
return compactToolResult(result, jmap);
|
|
1702
|
-
}
|
|
1703
|
-
});
|
|
1704
|
-
tools.dsers_rules_validate = aiTool({
|
|
1705
|
-
description: "Validate pricing/content/image rules before importing.",
|
|
1706
|
-
inputSchema: z2.object({
|
|
1707
|
-
rules_json: z2.string().describe("Rules as JSON string"),
|
|
1708
|
-
target_store: z2.string().optional().describe("Store ID or name")
|
|
1709
|
-
}),
|
|
1710
|
-
execute: async (input) => {
|
|
1711
|
-
audit("dsers_rules_validate", "mcp");
|
|
1712
|
-
let rules;
|
|
1713
|
-
try {
|
|
1714
|
-
rules = JSON.parse(input.rules_json);
|
|
1715
|
-
} catch {
|
|
1716
|
-
throw new Error("Invalid JSON in rules_json");
|
|
1717
|
-
}
|
|
1718
|
-
const result = await withTimeout(svc.validateRules({ rules, target_store: input.target_store ?? null }), 3e4, "Rule validation");
|
|
1719
|
-
audit("dsers_rules_validate", "mcp", void 0, "success");
|
|
1720
|
-
return compactToolResult(result, jmap);
|
|
1721
|
-
}
|
|
1722
|
-
});
|
|
1723
|
-
tools.dsers_job_status = aiTool({
|
|
1724
|
-
description: "Check job status: preview_ready \u2192 push_requested \u2192 completed/failed.",
|
|
1725
|
-
inputSchema: z2.object({
|
|
1726
|
-
job_id: z2.string().describe("Job ID")
|
|
1727
|
-
}),
|
|
1728
|
-
execute: async (input) => {
|
|
1729
|
-
audit("dsers_job_status", "mcp", input);
|
|
1730
|
-
const result = await withTimeout(svc.getJobStatus({ ...input, job_id: resolveJid(input.job_id) }), 3e4, "Job status");
|
|
1731
|
-
audit("dsers_job_status", "mcp", void 0, "success");
|
|
1732
|
-
return compactToolResult(result, jmap);
|
|
1733
|
-
}
|
|
1734
|
-
});
|
|
1735
|
-
tools.dsers_product_visibility = aiTool({
|
|
1736
|
-
description: "Set job visibility: backend_only (draft) or sell_immediately (live).",
|
|
1737
|
-
inputSchema: z2.object({
|
|
1738
|
-
job_id: z2.string().describe("Job ID"),
|
|
1739
|
-
visibility_mode: z2.string().describe("backend_only|sell_immediately")
|
|
1740
|
-
}),
|
|
1741
|
-
execute: async (input) => {
|
|
1742
|
-
audit("dsers_product_visibility", "mcp", input);
|
|
1743
|
-
const result = await withTimeout(svc.setProductVisibility({ ...input, job_id: resolveJid(input.job_id) }), 3e4, "Set visibility");
|
|
1744
|
-
audit("dsers_product_visibility", "mcp", void 0, "success");
|
|
1745
|
-
return compactToolResult(result, jmap);
|
|
1746
|
-
}
|
|
1747
|
-
});
|
|
1748
|
-
tools.dsers_product_delete = aiTool({
|
|
1749
|
-
description: "Delete product from import list. Call without confirm first, then with confirm=true after user approves.",
|
|
1750
|
-
inputSchema: z2.object({
|
|
1751
|
-
import_item_id: z2.string().describe("Item ID from dsers_import_list_search"),
|
|
1752
|
-
confirm: z2.boolean().optional().describe("true only after user approves")
|
|
1753
|
-
}),
|
|
1754
|
-
execute: async (input) => {
|
|
1755
|
-
audit("dsers_product_delete", "mcp", input);
|
|
1756
|
-
const result = await withTimeout(svc.deleteImportItem(input), 3e4, "Delete import item");
|
|
1757
|
-
audit("dsers_product_delete", "mcp", void 0, "success");
|
|
1758
|
-
return compactToolResult(result, jmap);
|
|
1759
|
-
}
|
|
1760
|
-
});
|
|
1445
|
+
if (this.mcpClient) {
|
|
1446
|
+
const mcpTools = await this.mcpClient.tools();
|
|
1447
|
+
Object.assign(tools, mcpTools);
|
|
1761
1448
|
} else {
|
|
1762
|
-
log7.warn("MCP
|
|
1449
|
+
log7.warn("MCP client not connected \u2014 no product tools available");
|
|
1763
1450
|
}
|
|
1764
1451
|
if (this.authCallback) {
|
|
1765
1452
|
tools.dsers_authorize = aiTool({
|
|
@@ -1771,139 +1458,13 @@ ${mems.map((m2) => `- ${m2.content}`).join("\n")}` : "";
|
|
|
1771
1458
|
}
|
|
1772
1459
|
});
|
|
1773
1460
|
}
|
|
1774
|
-
tools.dsers_import_list_search = aiTool({
|
|
1775
|
-
description: "Search import list. Returns items with id, source_url, cost_range, sell_price_range. Use source_url with dsers_product_import for modifications.",
|
|
1776
|
-
inputSchema: z2.object({
|
|
1777
|
-
page: z2.number().optional().describe("Page number (default 1)"),
|
|
1778
|
-
pageSize: z2.number().optional().describe("Items per page (default 20)"),
|
|
1779
|
-
keyword: z2.string().optional().describe("Search keyword")
|
|
1780
|
-
}),
|
|
1781
|
-
execute: async (input) => {
|
|
1782
|
-
audit("dsers_import_list_search", "dsers:product", input);
|
|
1783
|
-
const result = await withTimeout(getImportList(dsers, input), 3e4, "Search import list");
|
|
1784
|
-
audit("dsers_import_list_search", "dsers:product", void 0, "success");
|
|
1785
|
-
const items = result?.data?.list ?? result?.data ?? [];
|
|
1786
|
-
if (Array.isArray(items) && items.length > 0) {
|
|
1787
|
-
const enriched = await Promise.allSettled(
|
|
1788
|
-
items.slice(0, 10).map(async (item) => {
|
|
1789
|
-
const itemId = item.id ?? item.importListId;
|
|
1790
|
-
if (!itemId) return item;
|
|
1791
|
-
try {
|
|
1792
|
-
const detail = await withTimeout(
|
|
1793
|
-
getImportListItem(dsers, String(itemId)),
|
|
1794
|
-
15e3,
|
|
1795
|
-
"Item detail"
|
|
1796
|
-
);
|
|
1797
|
-
const detailData = detail?.data ?? detail;
|
|
1798
|
-
const variants = detailData?.variants ?? detailData?.skuList ?? detailData?.variantList ?? [];
|
|
1799
|
-
if (!Array.isArray(variants) || !variants.length) return item;
|
|
1800
|
-
const sellPrices = [];
|
|
1801
|
-
const costPrices = [];
|
|
1802
|
-
for (const v of variants) {
|
|
1803
|
-
const rawSell = Number(v.sellPrice ?? v.salePrice ?? v.price);
|
|
1804
|
-
const rawCost = Number(v.supplierPrice ?? v.buyPrice ?? v.cost);
|
|
1805
|
-
const sell = rawSell > 100 ? rawSell / 100 : rawSell;
|
|
1806
|
-
const cost = rawCost > 100 ? rawCost / 100 : rawCost;
|
|
1807
|
-
if (Number.isFinite(sell) && sell > 0) sellPrices.push(sell);
|
|
1808
|
-
if (Number.isFinite(cost) && cost > 0) costPrices.push(cost);
|
|
1809
|
-
}
|
|
1810
|
-
const enrichedItem = { ...item };
|
|
1811
|
-
const supplyProductId = detailData?.supplyProductId ?? item?.supplyProductId;
|
|
1812
|
-
const supplyAppId = Number(detailData?.supplyAppId ?? item?.supplyAppId ?? 1);
|
|
1813
|
-
if (supplyProductId) {
|
|
1814
|
-
if (supplyAppId === 2) {
|
|
1815
|
-
enrichedItem.source_url = `https://www.temu.com/product/${supplyProductId}.html`;
|
|
1816
|
-
} else if (supplyAppId === 3) {
|
|
1817
|
-
enrichedItem.source_url = `https://detail.1688.com/offer/${supplyProductId}.html`;
|
|
1818
|
-
} else {
|
|
1819
|
-
enrichedItem.source_url = `https://www.aliexpress.com/item/${supplyProductId}.html`;
|
|
1820
|
-
}
|
|
1821
|
-
}
|
|
1822
|
-
if (costPrices.length) {
|
|
1823
|
-
enrichedItem.cost_range = `$${Math.min(...costPrices).toFixed(2)} \u2013 $${Math.max(...costPrices).toFixed(2)}`;
|
|
1824
|
-
}
|
|
1825
|
-
if (sellPrices.length) {
|
|
1826
|
-
enrichedItem.sell_price_range = `$${Math.min(...sellPrices).toFixed(2)} \u2013 $${Math.max(...sellPrices).toFixed(2)}`;
|
|
1827
|
-
enrichedItem.no_markup = sellPrices.every((s, i) => Math.abs(s - (costPrices[i] ?? s)) < 0.01);
|
|
1828
|
-
}
|
|
1829
|
-
enrichedItem.variant_count = variants.length;
|
|
1830
|
-
let totalStock = 0;
|
|
1831
|
-
const lowStockVariants = [];
|
|
1832
|
-
for (const v of variants) {
|
|
1833
|
-
const stock = Number(v.stock ?? v.quantity ?? v.inventory ?? 0);
|
|
1834
|
-
const safeStock = Number.isFinite(stock) ? stock : 0;
|
|
1835
|
-
totalStock += safeStock;
|
|
1836
|
-
if (safeStock <= 5) {
|
|
1837
|
-
lowStockVariants.push({
|
|
1838
|
-
id: String(v.id ?? v.variantId ?? v.skuId ?? ""),
|
|
1839
|
-
sku: String(v.sku ?? v.skuAttr ?? v.optionName ?? v.title ?? ""),
|
|
1840
|
-
stock: safeStock
|
|
1841
|
-
});
|
|
1842
|
-
}
|
|
1843
|
-
}
|
|
1844
|
-
enrichedItem.total_stock = totalStock;
|
|
1845
|
-
if (lowStockVariants.length > 0) {
|
|
1846
|
-
enrichedItem.low_stock_variants = lowStockVariants;
|
|
1847
|
-
enrichedItem.low_stock_warning = `${lowStockVariants.length} of ${variants.length} variants have stock \u2264 5`;
|
|
1848
|
-
}
|
|
1849
|
-
return enrichedItem;
|
|
1850
|
-
} catch (err) {
|
|
1851
|
-
log7.warn({ err, itemId }, "Failed to enrich import list item");
|
|
1852
|
-
return item;
|
|
1853
|
-
}
|
|
1854
|
-
})
|
|
1855
|
-
);
|
|
1856
|
-
const enrichedItems = enriched.map((r) => r.status === "fulfilled" ? r.value : items[0]);
|
|
1857
|
-
const cleanItems = enrichedItems.map((item) => ({
|
|
1858
|
-
id: item.id ?? item.importListId,
|
|
1859
|
-
title: item.title ?? "(untitled)",
|
|
1860
|
-
source_url: item.source_url,
|
|
1861
|
-
cost_range: item.cost_range,
|
|
1862
|
-
sell_price_range: item.sell_price_range,
|
|
1863
|
-
no_markup: item.no_markup,
|
|
1864
|
-
variant_count: item.variant_count,
|
|
1865
|
-
total_stock: item.total_stock,
|
|
1866
|
-
low_stock_warning: item.low_stock_warning,
|
|
1867
|
-
low_stock_variants: item.low_stock_variants
|
|
1868
|
-
}));
|
|
1869
|
-
return { items: cleanItems, total: result?.data?.total ?? cleanItems.length };
|
|
1870
|
-
}
|
|
1871
|
-
const rawItems = result?.data ?? [];
|
|
1872
|
-
if (Array.isArray(rawItems)) {
|
|
1873
|
-
return { items: rawItems.map((item) => ({
|
|
1874
|
-
id: item.id ?? item.importListId,
|
|
1875
|
-
title: item.title ?? "(untitled)"
|
|
1876
|
-
})), total: rawItems.length };
|
|
1877
|
-
}
|
|
1878
|
-
return compactToolResult(result);
|
|
1879
|
-
}
|
|
1880
|
-
});
|
|
1881
|
-
tools.dsers_my_products = aiTool({
|
|
1882
|
-
description: "Get products already pushed to stores.",
|
|
1883
|
-
inputSchema: z2.object({
|
|
1884
|
-
page: z2.number().optional(),
|
|
1885
|
-
pageSize: z2.number().optional(),
|
|
1886
|
-
keyword: z2.string().optional(),
|
|
1887
|
-
storeId: z2.string().optional()
|
|
1888
|
-
}),
|
|
1889
|
-
execute: async (input) => {
|
|
1890
|
-
audit("dsers_my_products", "dsers:product", input);
|
|
1891
|
-
const result = await withTimeout(getMyProducts(dsers, input), 3e4, "My products");
|
|
1892
|
-
audit("dsers_my_products", "dsers:product", void 0, "success");
|
|
1893
|
-
return compactToolResult(result);
|
|
1894
|
-
}
|
|
1895
|
-
});
|
|
1896
1461
|
return tools;
|
|
1897
1462
|
}
|
|
1898
1463
|
};
|
|
1899
1464
|
|
|
1900
|
-
// src/gateway/gateway.ts
|
|
1901
|
-
import { MemoryJobStore as MemoryJobStore2 } from "@lofder/dsers-mcp-product/dist/job-store-memory.js";
|
|
1902
|
-
|
|
1903
1465
|
// src/web/server.ts
|
|
1904
|
-
init_logger();
|
|
1905
1466
|
import { createServer } from "http";
|
|
1906
|
-
import { readFileSync as
|
|
1467
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, existsSync as existsSync6, statSync, readdirSync } from "fs";
|
|
1907
1468
|
import { join as join7, dirname as dirname2, extname } from "path";
|
|
1908
1469
|
import { fileURLToPath } from "url";
|
|
1909
1470
|
import { homedir as homedir3 } from "os";
|
|
@@ -1932,7 +1493,7 @@ function findWebDir() {
|
|
|
1932
1493
|
join7(thisDir, "..", "..", "dist", "web")
|
|
1933
1494
|
];
|
|
1934
1495
|
for (const d of candidates) {
|
|
1935
|
-
if (
|
|
1496
|
+
if (existsSync6(join7(d, "index.html"))) return d;
|
|
1936
1497
|
}
|
|
1937
1498
|
throw new Error(
|
|
1938
1499
|
`Web build not found (index.html). Looked in:
|
|
@@ -1971,7 +1532,7 @@ var WebChatServer = class _WebChatServer {
|
|
|
1971
1532
|
}
|
|
1972
1533
|
tryListen(tryPort, webDir) {
|
|
1973
1534
|
return new Promise((resolve) => {
|
|
1974
|
-
const indexHtml =
|
|
1535
|
+
const indexHtml = readFileSync4(join7(webDir, "index.html"), "utf-8");
|
|
1975
1536
|
const srv = createServer((req, res) => {
|
|
1976
1537
|
if (req.url === "/health") {
|
|
1977
1538
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
@@ -1984,11 +1545,11 @@ var WebChatServer = class _WebChatServer {
|
|
|
1984
1545
|
const fileName = urlPath.slice("/uploads/".length);
|
|
1985
1546
|
if (/^[a-zA-Z0-9._-]+$/.test(fileName)) {
|
|
1986
1547
|
const filePath2 = join7(uploadsDir, fileName);
|
|
1987
|
-
if (
|
|
1548
|
+
if (existsSync6(filePath2) && statSync(filePath2).isFile()) {
|
|
1988
1549
|
const ext = extname(filePath2);
|
|
1989
1550
|
const mime = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
1990
1551
|
res.writeHead(200, { "Content-Type": mime, "Cache-Control": "public, max-age=86400" });
|
|
1991
|
-
res.end(
|
|
1552
|
+
res.end(readFileSync4(filePath2));
|
|
1992
1553
|
return;
|
|
1993
1554
|
}
|
|
1994
1555
|
}
|
|
@@ -1998,12 +1559,12 @@ var WebChatServer = class _WebChatServer {
|
|
|
1998
1559
|
}
|
|
1999
1560
|
if (urlPath !== "/" && urlPath !== "/index.html") {
|
|
2000
1561
|
const filePath2 = join7(webDir, urlPath);
|
|
2001
|
-
if (
|
|
1562
|
+
if (existsSync6(filePath2) && statSync(filePath2).isFile()) {
|
|
2002
1563
|
const ext = extname(filePath2);
|
|
2003
1564
|
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
2004
1565
|
const cacheHeader = urlPath.includes("/assets/") ? "public, max-age=31536000, immutable" : "no-cache";
|
|
2005
1566
|
res.writeHead(200, { "Content-Type": contentType, "Cache-Control": cacheHeader });
|
|
2006
|
-
res.end(
|
|
1567
|
+
res.end(readFileSync4(filePath2));
|
|
2007
1568
|
return;
|
|
2008
1569
|
}
|
|
2009
1570
|
}
|
|
@@ -2217,7 +1778,7 @@ var WebChatServer = class _WebChatServer {
|
|
|
2217
1778
|
// ─── File Browsing ───────────────────────────────────────────
|
|
2218
1779
|
buildFileTree() {
|
|
2219
1780
|
const configDir = join7(homedir3(), ".dsclaw");
|
|
2220
|
-
if (!
|
|
1781
|
+
if (!existsSync6(configDir)) return [];
|
|
2221
1782
|
const scanDir = (dirPath, depth = 0) => {
|
|
2222
1783
|
if (depth > 5) return [];
|
|
2223
1784
|
try {
|
|
@@ -2265,7 +1826,7 @@ var WebChatServer = class _WebChatServer {
|
|
|
2265
1826
|
return;
|
|
2266
1827
|
}
|
|
2267
1828
|
try {
|
|
2268
|
-
if (!
|
|
1829
|
+
if (!existsSync6(filePath2) || !statSync(filePath2).isFile()) {
|
|
2269
1830
|
this.wsSend(ws, { type: "file_content", path: filePath2, content: "[\u6587\u4EF6\u4E0D\u5B58\u5728]" });
|
|
2270
1831
|
return;
|
|
2271
1832
|
}
|
|
@@ -2276,13 +1837,13 @@ var WebChatServer = class _WebChatServer {
|
|
|
2276
1837
|
}
|
|
2277
1838
|
const ext = extname(filePath2).toLowerCase();
|
|
2278
1839
|
if (_WebChatServer.IMAGE_EXTS.has(ext)) {
|
|
2279
|
-
const buf =
|
|
1840
|
+
const buf = readFileSync4(filePath2);
|
|
2280
1841
|
const mime = _WebChatServer.IMAGE_MIME[ext] ?? "application/octet-stream";
|
|
2281
1842
|
const dataUrl = `data:${mime};base64,${buf.toString("base64")}`;
|
|
2282
1843
|
this.wsSend(ws, { type: "file_content", path: filePath2, content: dataUrl, isImage: true });
|
|
2283
1844
|
return;
|
|
2284
1845
|
}
|
|
2285
|
-
const content =
|
|
1846
|
+
const content = readFileSync4(filePath2, "utf-8");
|
|
2286
1847
|
this.wsSend(ws, { type: "file_content", path: filePath2, content });
|
|
2287
1848
|
} catch (err) {
|
|
2288
1849
|
const reason = err instanceof Error ? err.message : "unknown";
|
|
@@ -2306,7 +1867,6 @@ var WebChatServer = class _WebChatServer {
|
|
|
2306
1867
|
};
|
|
2307
1868
|
|
|
2308
1869
|
// src/channels/telegram.ts
|
|
2309
|
-
init_logger();
|
|
2310
1870
|
import { Bot, InlineKeyboard } from "grammy";
|
|
2311
1871
|
import { nanoid as nanoid3 } from "nanoid";
|
|
2312
1872
|
var log9 = createLogger("channel:telegram");
|
|
@@ -2432,23 +1992,22 @@ ${card.summary}`;
|
|
|
2432
1992
|
|
|
2433
1993
|
// src/memory/file-provider.ts
|
|
2434
1994
|
import {
|
|
2435
|
-
readFileSync as
|
|
1995
|
+
readFileSync as readFileSync5,
|
|
2436
1996
|
writeFileSync as writeFileSync6,
|
|
2437
1997
|
appendFileSync as appendFileSync2,
|
|
2438
|
-
existsSync as
|
|
1998
|
+
existsSync as existsSync7,
|
|
2439
1999
|
mkdirSync as mkdirSync7,
|
|
2440
2000
|
readdirSync as readdirSync2
|
|
2441
2001
|
} from "fs";
|
|
2442
2002
|
import { join as join8 } from "path";
|
|
2443
2003
|
import { randomUUID } from "crypto";
|
|
2444
|
-
init_logger();
|
|
2445
2004
|
var log10 = createLogger("memory:file");
|
|
2446
2005
|
var MEMORY_DIR = join8(CONFIG_DIR, "memories");
|
|
2447
2006
|
var FileMemoryProvider = class {
|
|
2448
2007
|
name = "file";
|
|
2449
2008
|
degraded = false;
|
|
2450
2009
|
constructor() {
|
|
2451
|
-
if (!
|
|
2010
|
+
if (!existsSync7(MEMORY_DIR)) {
|
|
2452
2011
|
mkdirSync7(MEMORY_DIR, { recursive: true });
|
|
2453
2012
|
}
|
|
2454
2013
|
}
|
|
@@ -2484,8 +2043,8 @@ var FileMemoryProvider = class {
|
|
|
2484
2043
|
const files = filters.userId ? [this.userFile(filters.userId)] : readdirSync2(MEMORY_DIR).filter((f) => f.endsWith(".jsonl")).map((f) => join8(MEMORY_DIR, f));
|
|
2485
2044
|
const results = [];
|
|
2486
2045
|
for (const file of files) {
|
|
2487
|
-
if (!
|
|
2488
|
-
const lines =
|
|
2046
|
+
if (!existsSync7(file)) continue;
|
|
2047
|
+
const lines = readFileSync5(file, "utf-8").split("\n").filter((l) => l.trim());
|
|
2489
2048
|
for (const line of lines) {
|
|
2490
2049
|
try {
|
|
2491
2050
|
const stored = JSON.parse(line);
|
|
@@ -2508,8 +2067,8 @@ var FileMemoryProvider = class {
|
|
|
2508
2067
|
async update(id, content) {
|
|
2509
2068
|
const files = readdirSync2(MEMORY_DIR).filter((f) => f.endsWith(".jsonl")).map((f) => join8(MEMORY_DIR, f));
|
|
2510
2069
|
for (const file of files) {
|
|
2511
|
-
if (!
|
|
2512
|
-
const lines =
|
|
2070
|
+
if (!existsSync7(file)) continue;
|
|
2071
|
+
const lines = readFileSync5(file, "utf-8").split("\n").filter((l) => l.trim());
|
|
2513
2072
|
const updated = lines.map((line) => {
|
|
2514
2073
|
try {
|
|
2515
2074
|
const stored = JSON.parse(line);
|
|
@@ -2528,8 +2087,8 @@ var FileMemoryProvider = class {
|
|
|
2528
2087
|
async delete(id) {
|
|
2529
2088
|
const files = readdirSync2(MEMORY_DIR).filter((f) => f.endsWith(".jsonl")).map((f) => join8(MEMORY_DIR, f));
|
|
2530
2089
|
for (const file of files) {
|
|
2531
|
-
if (!
|
|
2532
|
-
const lines =
|
|
2090
|
+
if (!existsSync7(file)) continue;
|
|
2091
|
+
const lines = readFileSync5(file, "utf-8").split("\n").filter((l) => l.trim());
|
|
2533
2092
|
const filtered = lines.filter((line) => {
|
|
2534
2093
|
try {
|
|
2535
2094
|
const stored = JSON.parse(line);
|
|
@@ -2547,12 +2106,7 @@ var FileMemoryProvider = class {
|
|
|
2547
2106
|
}
|
|
2548
2107
|
};
|
|
2549
2108
|
|
|
2550
|
-
// src/gateway/gateway.ts
|
|
2551
|
-
init_logger();
|
|
2552
|
-
init_tracer();
|
|
2553
|
-
|
|
2554
2109
|
// src/resilience/degradation.ts
|
|
2555
|
-
init_logger();
|
|
2556
2110
|
var log11 = createLogger("degradation");
|
|
2557
2111
|
var states = /* @__PURE__ */ new Map();
|
|
2558
2112
|
function markDegraded(service, reason) {
|
|
@@ -2597,7 +2151,6 @@ function degradationMessage(service) {
|
|
|
2597
2151
|
}
|
|
2598
2152
|
|
|
2599
2153
|
// src/context/token-counter.ts
|
|
2600
|
-
init_logger();
|
|
2601
2154
|
var log12 = createLogger("context:token");
|
|
2602
2155
|
var encode = null;
|
|
2603
2156
|
function countTokens(text) {
|
|
@@ -2609,7 +2162,6 @@ function countTokens(text) {
|
|
|
2609
2162
|
}
|
|
2610
2163
|
|
|
2611
2164
|
// src/context/context-budget.ts
|
|
2612
|
-
init_logger();
|
|
2613
2165
|
var log13 = createLogger("context:budget");
|
|
2614
2166
|
var FALLBACK_MAX_MESSAGES = 20;
|
|
2615
2167
|
var DEFAULT_BUDGET = {
|
|
@@ -2668,7 +2220,6 @@ function allocateBudget(systemPrompt, memories, history, config = {}) {
|
|
|
2668
2220
|
}
|
|
2669
2221
|
|
|
2670
2222
|
// src/context/compaction.ts
|
|
2671
|
-
init_logger();
|
|
2672
2223
|
var log14 = createLogger("context:compact");
|
|
2673
2224
|
async function compactWithFlush(allMessages, keepCount, memory, userId) {
|
|
2674
2225
|
if (allMessages.length <= keepCount) {
|
|
@@ -2757,49 +2308,48 @@ function extractKeyFacts(messages) {
|
|
|
2757
2308
|
|
|
2758
2309
|
// src/context/history-store.ts
|
|
2759
2310
|
import {
|
|
2760
|
-
readFileSync as
|
|
2311
|
+
readFileSync as readFileSync6,
|
|
2761
2312
|
writeFileSync as writeFileSync7,
|
|
2762
|
-
existsSync as
|
|
2313
|
+
existsSync as existsSync8,
|
|
2763
2314
|
mkdirSync as mkdirSync8,
|
|
2764
|
-
renameSync
|
|
2315
|
+
renameSync
|
|
2765
2316
|
} from "fs";
|
|
2766
2317
|
import { join as join9 } from "path";
|
|
2767
2318
|
import {
|
|
2768
|
-
randomBytes as
|
|
2769
|
-
createCipheriv as
|
|
2319
|
+
randomBytes as randomBytes3,
|
|
2320
|
+
createCipheriv as createCipheriv3,
|
|
2770
2321
|
createDecipheriv as createDecipheriv2,
|
|
2771
|
-
createHash as
|
|
2322
|
+
createHash as createHash4
|
|
2772
2323
|
} from "crypto";
|
|
2773
|
-
import { hostname as
|
|
2774
|
-
init_logger();
|
|
2324
|
+
import { hostname as hostname3, userInfo as userInfo3 } from "os";
|
|
2775
2325
|
var log15 = createLogger("context:history");
|
|
2776
2326
|
var HISTORY_DIR = join9(CONFIG_DIR, "history");
|
|
2777
2327
|
var MAX_LINES = 200;
|
|
2778
2328
|
var KEEP_AFTER_ROTATE = 100;
|
|
2779
|
-
var
|
|
2780
|
-
var
|
|
2781
|
-
var
|
|
2329
|
+
var ALG3 = "aes-256-gcm";
|
|
2330
|
+
var IV_LEN3 = 12;
|
|
2331
|
+
var TAG_LEN3 = 16;
|
|
2782
2332
|
var KEY_SEED2 = "dsclaw-history-v1";
|
|
2783
|
-
function
|
|
2333
|
+
function deriveKey3() {
|
|
2784
2334
|
let user = "";
|
|
2785
2335
|
try {
|
|
2786
|
-
user =
|
|
2336
|
+
user = userInfo3().username;
|
|
2787
2337
|
} catch {
|
|
2788
2338
|
user = process.env["USER"] ?? process.env["USERNAME"] ?? "default";
|
|
2789
2339
|
}
|
|
2790
|
-
return
|
|
2340
|
+
return createHash4("sha256").update(`${KEY_SEED2}:${hostname3()}:${user}`).digest();
|
|
2791
2341
|
}
|
|
2792
2342
|
function encryptStr(data) {
|
|
2793
|
-
const key =
|
|
2794
|
-
const iv =
|
|
2795
|
-
const cipher =
|
|
2343
|
+
const key = deriveKey3();
|
|
2344
|
+
const iv = randomBytes3(IV_LEN3);
|
|
2345
|
+
const cipher = createCipheriv3(ALG3, key, iv, { authTagLength: TAG_LEN3 });
|
|
2796
2346
|
const ct = Buffer.concat([cipher.update(data, "utf8"), cipher.final()]);
|
|
2797
2347
|
const tag = cipher.getAuthTag();
|
|
2798
2348
|
return Buffer.concat([iv, tag, ct]).toString("base64");
|
|
2799
2349
|
}
|
|
2800
2350
|
var cache = /* @__PURE__ */ new Map();
|
|
2801
2351
|
function ensureDir() {
|
|
2802
|
-
if (!
|
|
2352
|
+
if (!existsSync8(HISTORY_DIR)) {
|
|
2803
2353
|
mkdirSync8(HISTORY_DIR, { recursive: true });
|
|
2804
2354
|
}
|
|
2805
2355
|
}
|
|
@@ -2812,7 +2362,7 @@ function writeEncryptedFile(fp, messages) {
|
|
|
2812
2362
|
const encrypted = encryptStr(json);
|
|
2813
2363
|
const tmp = fp + ".tmp";
|
|
2814
2364
|
writeFileSync7(tmp, encrypted);
|
|
2815
|
-
|
|
2365
|
+
renameSync(tmp, fp);
|
|
2816
2366
|
}
|
|
2817
2367
|
function appendHistory(sessionKey, ...messages) {
|
|
2818
2368
|
const history = cache.get(sessionKey) ?? [];
|
|
@@ -3070,7 +2620,6 @@ var DSClawGateway = class {
|
|
|
3070
2620
|
webChannel = null;
|
|
3071
2621
|
memory;
|
|
3072
2622
|
agents = /* @__PURE__ */ new Map();
|
|
3073
|
-
jobStores = /* @__PURE__ */ new Map();
|
|
3074
2623
|
dsersClients = /* @__PURE__ */ new Map();
|
|
3075
2624
|
lastUserMessages = /* @__PURE__ */ new Map();
|
|
3076
2625
|
running = false;
|
|
@@ -3297,6 +2846,8 @@ var DSClawGateway = class {
|
|
|
3297
2846
|
session.dspiSessionState = void 0;
|
|
3298
2847
|
session.state = "new";
|
|
3299
2848
|
saveSession(userId, session);
|
|
2849
|
+
const agent = this.agents.get(userId);
|
|
2850
|
+
if (agent) await agent.destroy();
|
|
3300
2851
|
this.agents.delete(userId);
|
|
3301
2852
|
this.pushSettingsState(channel, userId, session);
|
|
3302
2853
|
break;
|
|
@@ -3549,8 +3100,9 @@ var DSClawGateway = class {
|
|
|
3549
3100
|
return;
|
|
3550
3101
|
}
|
|
3551
3102
|
if (cmd === "/logout") {
|
|
3103
|
+
const agent2 = this.agents.get(userId);
|
|
3104
|
+
if (agent2) await agent2.destroy();
|
|
3552
3105
|
this.agents.delete(userId);
|
|
3553
|
-
this.jobStores.delete(userId);
|
|
3554
3106
|
this.dsersClients.delete(userId);
|
|
3555
3107
|
this.lastUserMessages.delete(userId);
|
|
3556
3108
|
for (const key of sessionHistories.keys()) {
|
|
@@ -3590,7 +3142,7 @@ var DSClawGateway = class {
|
|
|
3590
3142
|
}
|
|
3591
3143
|
return;
|
|
3592
3144
|
}
|
|
3593
|
-
const agent = this.getOrCreateAgent(userId, session);
|
|
3145
|
+
const agent = await this.getOrCreateAgent(userId, session);
|
|
3594
3146
|
const sessionKey = `${channel.name}:${userId}`;
|
|
3595
3147
|
touchSession(sessionKey);
|
|
3596
3148
|
const history = sessionHistories.get(sessionKey) ?? [];
|
|
@@ -3839,7 +3391,7 @@ var DSClawGateway = class {
|
|
|
3839
3391
|
result: "success"
|
|
3840
3392
|
});
|
|
3841
3393
|
}
|
|
3842
|
-
getOrCreateAgent(userId, session) {
|
|
3394
|
+
async getOrCreateAgent(userId, session) {
|
|
3843
3395
|
let agent = this.agents.get(userId);
|
|
3844
3396
|
if (agent) return agent;
|
|
3845
3397
|
const dsersConfig = createDSersConfig(session.dspiEmail ?? "unknown");
|
|
@@ -3857,12 +3409,10 @@ var DSClawGateway = class {
|
|
|
3857
3409
|
};
|
|
3858
3410
|
const dsersSession = session.dspiSessionId ? { sessionId: session.dspiSessionId, state: session.dspiSessionState ?? "" } : void 0;
|
|
3859
3411
|
const authCb = () => this.triggerAuth(userId);
|
|
3860
|
-
|
|
3861
|
-
if (
|
|
3862
|
-
|
|
3863
|
-
this.jobStores.set(userId, jobStore);
|
|
3412
|
+
agent = new DSClawCoreAgent(dsersClient, this.memory, llm, authCb);
|
|
3413
|
+
if (dsersSession) {
|
|
3414
|
+
await agent.initMCP(dsersSession);
|
|
3864
3415
|
}
|
|
3865
|
-
agent = new DSClawCoreAgent(dsersClient, this.memory, llm, dsersSession, authCb, jobStore);
|
|
3866
3416
|
this.agents.set(userId, agent);
|
|
3867
3417
|
if (!agent.mcpAvailable) {
|
|
3868
3418
|
log16.warn({ userId }, "MCP unavailable for agent \u2014 notifying user");
|
|
@@ -3892,7 +3442,6 @@ var DSClawGateway = class {
|
|
|
3892
3442
|
};
|
|
3893
3443
|
|
|
3894
3444
|
// src/resilience/circuit-breaker.ts
|
|
3895
|
-
init_logger();
|
|
3896
3445
|
import CircuitBreaker from "opossum";
|
|
3897
3446
|
var log17 = createLogger("circuit-breaker");
|
|
3898
3447
|
var PRESETS = {
|
|
@@ -3957,7 +3506,6 @@ function getAllBreakerStatus() {
|
|
|
3957
3506
|
}
|
|
3958
3507
|
|
|
3959
3508
|
// src/resilience/retry.ts
|
|
3960
|
-
init_logger();
|
|
3961
3509
|
var log18 = createLogger("retry");
|
|
3962
3510
|
var DEFAULT_OPTIONS = {
|
|
3963
3511
|
maxRetries: 2,
|
|
@@ -4029,7 +3577,6 @@ var DSCLAW_MANIFEST = {
|
|
|
4029
3577
|
};
|
|
4030
3578
|
|
|
4031
3579
|
// src/plugins/compat.ts
|
|
4032
|
-
init_logger();
|
|
4033
3580
|
var log19 = createLogger("compat");
|
|
4034
3581
|
var COMPAT_MATRIX = [
|
|
4035
3582
|
{
|