@ynhcj/xiaoyi-channel 0.0.185-beta → 0.0.185-next
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/index.js +141 -1
- package/dist/src/acp-session-binding.d.ts +37 -0
- package/dist/src/acp-session-binding.js +237 -0
- package/dist/src/bot.js +62 -33
- package/dist/src/channel.js +68 -64
- package/dist/src/client.d.ts +5 -0
- package/dist/src/client.js +10 -0
- package/dist/src/cron-command.d.ts +2 -0
- package/dist/src/cron-command.js +14 -8
- package/dist/src/cron-query-handler.js +36 -0
- package/dist/src/cspl/call_api.js +4 -2
- package/dist/src/cspl/sentinel_hook.js +9 -8
- package/dist/src/cspl/upload_file.js +2 -2
- package/dist/src/formatter.js +72 -36
- package/dist/src/log-reporter/config-loader.d.ts +11 -0
- package/dist/src/log-reporter/config-loader.js +68 -0
- package/dist/src/log-reporter/cursor-store.d.ts +5 -0
- package/dist/src/log-reporter/cursor-store.js +26 -0
- package/dist/src/log-reporter/index.d.ts +10 -0
- package/dist/src/log-reporter/index.js +77 -0
- package/dist/src/log-reporter/reporter.d.ts +6 -0
- package/dist/src/log-reporter/reporter.js +17 -0
- package/dist/src/log-reporter/scanner.d.ts +6 -0
- package/dist/src/log-reporter/scanner.js +82 -0
- package/dist/src/log-reporter/types.d.ts +59 -0
- package/dist/src/log-reporter/types.js +2 -0
- package/dist/src/log-reporter/uploader.d.ts +6 -0
- package/dist/src/log-reporter/uploader.js +32 -0
- package/dist/src/memory-query-handler.d.ts +1 -0
- package/dist/src/memory-query-handler.js +283 -0
- package/dist/src/monitor.js +29 -9
- package/dist/src/outbound.js +5 -0
- package/dist/src/parser.d.ts +12 -0
- package/dist/src/parser.js +39 -13
- package/dist/src/provider.js +79 -16
- package/dist/src/reply-dispatcher.d.ts +3 -0
- package/dist/src/reply-dispatcher.js +166 -164
- package/dist/src/self-evolution-handler.js +2 -1
- package/dist/src/task-manager.d.ts +5 -6
- package/dist/src/task-manager.js +5 -9
- package/dist/src/tools/agent-as-skill-tool.d.ts +41 -2
- package/dist/src/tools/agent-as-skill-tool.js +144 -151
- package/dist/src/tools/calendar-tool.d.ts +24 -2
- package/dist/src/tools/calendar-tool.js +115 -117
- package/dist/src/tools/call-device-tool.d.ts +20 -6
- package/dist/src/tools/call-device-tool.js +116 -138
- package/dist/src/tools/call-phone-tool.d.ts +21 -2
- package/dist/src/tools/call-phone-tool.js +112 -114
- package/dist/src/tools/check-plugin-privilege-tool.d.ts +16 -2
- package/dist/src/tools/check-plugin-privilege-tool.js +141 -143
- package/dist/src/tools/create-alarm-tool.d.ts +39 -2
- package/dist/src/tools/create-alarm-tool.js +229 -231
- package/dist/src/tools/create-all-tools.js +8 -4
- package/dist/src/tools/delete-alarm-tool.d.ts +15 -2
- package/dist/src/tools/delete-alarm-tool.js +134 -136
- package/dist/src/tools/device-tool-map.d.ts +1 -1
- package/dist/src/tools/device-tool-map.js +9 -2
- package/dist/src/tools/discover-cross-devices-tool.d.ts +16 -2
- package/dist/src/tools/discover-cross-devices-tool.js +121 -124
- package/dist/src/tools/display-a2ui-card-bypath-tool.d.ts +21 -0
- package/dist/src/tools/display-a2ui-card-bypath-tool.js +65 -0
- package/dist/src/tools/get-alarm-tool-schema.d.ts +1 -2
- package/dist/src/tools/get-alarm-tool-schema.js +10 -16
- package/dist/src/tools/get-calendar-tool-schema.d.ts +1 -2
- package/dist/src/tools/get-calendar-tool-schema.js +8 -12
- package/dist/src/tools/get-collection-tool-schema.d.ts +1 -2
- package/dist/src/tools/get-collection-tool-schema.js +9 -11
- package/dist/src/tools/get-contact-tool-schema.d.ts +1 -2
- package/dist/src/tools/get-contact-tool-schema.js +10 -16
- package/dist/src/tools/get-device-file-tool-schema.d.ts +1 -2
- package/dist/src/tools/get-device-file-tool-schema.js +9 -13
- package/dist/src/tools/get-email-tool-schema.d.ts +1 -2
- package/dist/src/tools/get-email-tool-schema.js +8 -11
- package/dist/src/tools/get-note-tool-schema.d.ts +1 -2
- package/dist/src/tools/get-note-tool-schema.js +9 -14
- package/dist/src/tools/get-photo-tool-schema.d.ts +1 -2
- package/dist/src/tools/get-photo-tool-schema.js +9 -12
- package/dist/src/tools/image-reading-tool.d.ts +28 -2
- package/dist/src/tools/image-reading-tool.js +76 -76
- package/dist/src/tools/location-tool.d.ts +11 -2
- package/dist/src/tools/location-tool.js +93 -92
- package/dist/src/tools/login-token-tool.d.ts +20 -2
- package/dist/src/tools/login-token-tool.js +123 -125
- package/dist/src/tools/modify-alarm-tool.d.ts +47 -2
- package/dist/src/tools/modify-alarm-tool.js +250 -252
- package/dist/src/tools/modify-note-tool.d.ts +20 -2
- package/dist/src/tools/modify-note-tool.js +107 -109
- package/dist/src/tools/note-tool.d.ts +20 -2
- package/dist/src/tools/note-tool.js +106 -108
- package/dist/src/tools/query-app-message-tool.d.ts +28 -2
- package/dist/src/tools/query-app-message-tool.js +111 -113
- package/dist/src/tools/query-memory-data-tool.d.ts +28 -2
- package/dist/src/tools/query-memory-data-tool.js +112 -114
- package/dist/src/tools/query-todo-task-tool.d.ts +24 -2
- package/dist/src/tools/query-todo-task-tool.js +106 -108
- package/dist/src/tools/save-file-to-phone-tool.d.ts +24 -2
- package/dist/src/tools/save-file-to-phone-tool.js +130 -132
- package/dist/src/tools/save-media-to-gallery-tool.d.ts +24 -2
- package/dist/src/tools/save-media-to-gallery-tool.js +137 -139
- package/dist/src/tools/save-self-evolution-skill-tool.d.ts +54 -2
- package/dist/src/tools/save-self-evolution-skill-tool.js +194 -194
- package/dist/src/tools/search-alarm-tool.d.ts +34 -2
- package/dist/src/tools/search-alarm-tool.js +174 -176
- package/dist/src/tools/search-calendar-tool.d.ts +24 -2
- package/dist/src/tools/search-calendar-tool.js +148 -150
- package/dist/src/tools/search-contact-tool.d.ts +16 -2
- package/dist/src/tools/search-contact-tool.js +101 -103
- package/dist/src/tools/search-email-tool.d.ts +21 -2
- package/dist/src/tools/search-email-tool.js +110 -112
- package/dist/src/tools/search-file-tool.d.ts +16 -2
- package/dist/src/tools/search-file-tool.js +104 -106
- package/dist/src/tools/search-message-tool.d.ts +16 -2
- package/dist/src/tools/search-message-tool.js +103 -105
- package/dist/src/tools/search-note-tool.d.ts +16 -2
- package/dist/src/tools/search-note-tool.js +98 -100
- package/dist/src/tools/search-photo-gallery-tool.d.ts +21 -2
- package/dist/src/tools/search-photo-gallery-tool.js +35 -37
- package/dist/src/tools/send-cross-device-task-tool.d.ts +35 -2
- package/dist/src/tools/send-cross-device-task-tool.js +216 -150
- package/dist/src/tools/send-email-tool.d.ts +24 -2
- package/dist/src/tools/send-email-tool.js +108 -110
- package/dist/src/tools/send-file-to-user-tool.d.ts +20 -2
- package/dist/src/tools/send-file-to-user-tool.js +174 -178
- package/dist/src/tools/send-html-card-tool.d.ts +20 -2
- package/dist/src/tools/send-html-card-tool.js +85 -87
- package/dist/src/tools/send-message-tool.d.ts +20 -2
- package/dist/src/tools/send-message-tool.js +122 -124
- package/dist/src/tools/session-manager.d.ts +19 -52
- package/dist/src/tools/session-manager.js +95 -247
- package/dist/src/tools/upload-file-tool.d.ts +20 -2
- package/dist/src/tools/upload-file-tool.js +80 -82
- package/dist/src/tools/upload-photo-tool.d.ts +20 -2
- package/dist/src/tools/upload-photo-tool.js +68 -70
- package/dist/src/tools/xiaoyi-add-collection-tool.d.ts +32 -2
- package/dist/src/tools/xiaoyi-add-collection-tool.js +146 -148
- package/dist/src/tools/xiaoyi-collection-tool.d.ts +20 -2
- package/dist/src/tools/xiaoyi-collection-tool.js +114 -116
- package/dist/src/tools/xiaoyi-delete-collection-tool.d.ts +15 -2
- package/dist/src/tools/xiaoyi-delete-collection-tool.js +127 -129
- package/dist/src/tools/xiaoyi-gui-tool.d.ts +16 -2
- package/dist/src/tools/xiaoyi-gui-tool.js +92 -95
- package/dist/src/types.d.ts +6 -6
- package/dist/src/utils/config-manager.d.ts +3 -2
- package/dist/src/utils/config-manager.js +22 -2
- package/dist/src/utils/cron-push-map.d.ts +26 -0
- package/dist/src/utils/cron-push-map.js +131 -0
- package/dist/src/utils/logger.js +3 -14
- package/dist/src/websocket.d.ts +1 -1
- package/dist/src/websocket.js +29 -13
- package/package.json +1 -1
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
// Memory query event handler.
|
|
2
|
+
// Listens for memory-query-event from the WebSocket manager,
|
|
3
|
+
// handles memory state read/write and MEMORY.md/USER.md file queries.
|
|
4
|
+
import * as os from "os";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
7
|
+
import { sendCommand } from "./formatter.js";
|
|
8
|
+
import { resolveXYConfig } from "./config.js";
|
|
9
|
+
import { logger } from "./utils/logger.js";
|
|
10
|
+
const XIAOYIRUNTIME_PATH_PRIMARY = "/home/sandbox/.openclaw/.xiaoyiruntime";
|
|
11
|
+
const XIAOYIRUNTIME_PATH_FALLBACK = `${os.homedir()}/.openclaw/.xiaoyiruntime`;
|
|
12
|
+
const MEMORY_STATE_KEY = "MEMORYSTATE";
|
|
13
|
+
/** Resolve writable .xiaoyiruntime path: try sandbox path first, fallback to user home. */
|
|
14
|
+
function resolveXiaoyiRuntimePath() {
|
|
15
|
+
try {
|
|
16
|
+
// If primary path's parent dir exists and is writable, use it
|
|
17
|
+
const fs = require("fs");
|
|
18
|
+
fs.accessSync(XIAOYIRUNTIME_PATH_PRIMARY, fs.constants.W_OK);
|
|
19
|
+
return XIAOYIRUNTIME_PATH_PRIMARY;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// If primary path doesn't exist, try creating parent
|
|
23
|
+
try {
|
|
24
|
+
const fs = require("fs");
|
|
25
|
+
const dir = require("path").dirname(XIAOYIRUNTIME_PATH_PRIMARY);
|
|
26
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
27
|
+
return XIAOYIRUNTIME_PATH_PRIMARY;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Fallback to user home
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return XIAOYIRUNTIME_PATH_FALLBACK;
|
|
34
|
+
}
|
|
35
|
+
export async function handleMemoryQueryEvent(context, cfg) {
|
|
36
|
+
const { action, params, sessionId, taskId, messageId } = context;
|
|
37
|
+
const log = logger.withContext(sessionId ?? "", taskId ?? "");
|
|
38
|
+
log.log(`[MEMORY-QUERY] Received event: action=${action}`);
|
|
39
|
+
let result;
|
|
40
|
+
try {
|
|
41
|
+
switch (action) {
|
|
42
|
+
case "MemoryStateSet":
|
|
43
|
+
result = handleMemoryStateSet(params);
|
|
44
|
+
break;
|
|
45
|
+
case "MemoryStateGet":
|
|
46
|
+
result = handleMemoryStateGet();
|
|
47
|
+
break;
|
|
48
|
+
case "UserMdQuery":
|
|
49
|
+
result = handleUserMdQuery();
|
|
50
|
+
break;
|
|
51
|
+
case "MemoryMdQuery":
|
|
52
|
+
result = handleMemoryMdQuery();
|
|
53
|
+
break;
|
|
54
|
+
case "MemoryHistory":
|
|
55
|
+
result = handleMemoryHistory();
|
|
56
|
+
break;
|
|
57
|
+
default:
|
|
58
|
+
log.error(`[MEMORY-QUERY] Unknown action: ${action}`);
|
|
59
|
+
result = { error: `Unknown action: ${action}` };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
64
|
+
log.error(`[MEMORY-QUERY] Handler failed for action=${action}:`, err);
|
|
65
|
+
result = { error: errorMsg };
|
|
66
|
+
}
|
|
67
|
+
log.log(`[MEMORY-QUERY] Result for action=${action}: ${JSON.stringify(result)}`);
|
|
68
|
+
// Send result back via sendCommand
|
|
69
|
+
if (cfg && sessionId && taskId && messageId) {
|
|
70
|
+
try {
|
|
71
|
+
const config = resolveXYConfig(cfg);
|
|
72
|
+
const command = {
|
|
73
|
+
header: {
|
|
74
|
+
namespace: "AgentEvent",
|
|
75
|
+
name: "MemoryQuery",
|
|
76
|
+
},
|
|
77
|
+
payload: {
|
|
78
|
+
action,
|
|
79
|
+
ans: result,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
await sendCommand({
|
|
83
|
+
config,
|
|
84
|
+
sessionId,
|
|
85
|
+
taskId,
|
|
86
|
+
messageId,
|
|
87
|
+
command,
|
|
88
|
+
final: true,
|
|
89
|
+
});
|
|
90
|
+
log.log(`[MEMORY-QUERY] Sent response via sendCommand, action=${action}`);
|
|
91
|
+
}
|
|
92
|
+
catch (sendErr) {
|
|
93
|
+
log.error(`[MEMORY-QUERY] Failed to send response via sendCommand:`, sendErr);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
log.warn(`[MEMORY-QUERY] Missing cfg/sessionId/taskId/messageId, skipping sendCommand`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Write MEMORYSTATE=true/false to .xiaoyiruntime.
|
|
102
|
+
*/
|
|
103
|
+
function handleMemoryStateSet(params) {
|
|
104
|
+
const memoryState = params?.memoryState;
|
|
105
|
+
if (typeof memoryState !== "boolean") {
|
|
106
|
+
logger.error(`[MEMORY-QUERY] memoryStateSet: invalid memoryState type: ${typeof memoryState}`);
|
|
107
|
+
return { code: 0 };
|
|
108
|
+
}
|
|
109
|
+
const value = String(memoryState);
|
|
110
|
+
const filePath = resolveXiaoyiRuntimePath();
|
|
111
|
+
let content;
|
|
112
|
+
try {
|
|
113
|
+
content = readFileSync(filePath, "utf-8");
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
logger.log(`[MEMORY-QUERY] ${filePath} not found, creating new file`);
|
|
117
|
+
writeFileSync(filePath, `${MEMORY_STATE_KEY}=${value}\n`, "utf-8");
|
|
118
|
+
logger.log(`[MEMORY-QUERY] wrote ${MEMORY_STATE_KEY}=${value}`);
|
|
119
|
+
return { code: 0 };
|
|
120
|
+
}
|
|
121
|
+
const lines = content.split("\n");
|
|
122
|
+
const key = MEMORY_STATE_KEY;
|
|
123
|
+
let found = false;
|
|
124
|
+
const updated = lines.map((line) => {
|
|
125
|
+
if (line.startsWith(`${key}=`)) {
|
|
126
|
+
found = true;
|
|
127
|
+
return `${key}=${value}`;
|
|
128
|
+
}
|
|
129
|
+
return line;
|
|
130
|
+
});
|
|
131
|
+
if (!found) {
|
|
132
|
+
const trimmed = content.trimEnd();
|
|
133
|
+
writeFileSync(filePath, `${trimmed}\n${key}=${value}\n`, "utf-8");
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
writeFileSync(filePath, updated.join("\n"), "utf-8");
|
|
137
|
+
}
|
|
138
|
+
logger.log(`[MEMORY-QUERY] updated ${MEMORY_STATE_KEY}=${value} in ${filePath}`);
|
|
139
|
+
return { code: 0 };
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Read MEMORYSTATE from .xiaoyiruntime and return its boolean value.
|
|
143
|
+
* Missing file or key defaults to false.
|
|
144
|
+
*/
|
|
145
|
+
function handleMemoryStateGet() {
|
|
146
|
+
const filePath = resolveXiaoyiRuntimePath();
|
|
147
|
+
let content;
|
|
148
|
+
try {
|
|
149
|
+
content = readFileSync(filePath, "utf-8");
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
if (err.code === "ENOENT") {
|
|
153
|
+
logger.log(`[MEMORY-QUERY] ${filePath} not found`);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
logger.error(`[MEMORY-QUERY] Failed to read ${filePath}:`, err);
|
|
157
|
+
}
|
|
158
|
+
return { memoryState: false };
|
|
159
|
+
}
|
|
160
|
+
for (const line of content.split("\n")) {
|
|
161
|
+
if (line.startsWith(`${MEMORY_STATE_KEY}=`)) {
|
|
162
|
+
const value = line.slice(`${MEMORY_STATE_KEY}=`.length).trim();
|
|
163
|
+
logger.log(`[MEMORY-QUERY] read ${MEMORY_STATE_KEY}=${value} from ${filePath}`);
|
|
164
|
+
return { memoryState: value === "true" };
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
logger.log(`[MEMORY-QUERY] ${MEMORY_STATE_KEY} not found in ${filePath}`);
|
|
168
|
+
return { memoryState: false };
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Read ~/.openclaw/workspace/USER.md and return content in fileDetail.
|
|
172
|
+
*/
|
|
173
|
+
function handleUserMdQuery() {
|
|
174
|
+
const filePath = path.join(os.homedir(), ".openclaw", "workspace", "USER.md");
|
|
175
|
+
return readMdFile(filePath);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Read ~/.openclaw/workspace/MEMORY.md and return content in fileDetail.
|
|
179
|
+
*/
|
|
180
|
+
function handleMemoryMdQuery() {
|
|
181
|
+
const filePath = path.join(os.homedir(), ".openclaw", "workspace", "MEMORY.md");
|
|
182
|
+
return readMdFile(filePath);
|
|
183
|
+
}
|
|
184
|
+
function readMdFile(filePath) {
|
|
185
|
+
try {
|
|
186
|
+
const content = readFileSync(filePath, "utf-8");
|
|
187
|
+
logger.log(`[MEMORY-QUERY] Read file: ${filePath}, size: ${content.length}`);
|
|
188
|
+
return { fileDetail: content };
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
if (err.code === "ENOENT") {
|
|
192
|
+
logger.log(`[MEMORY-QUERY] File not found: ${filePath}`);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
logger.error(`[MEMORY-QUERY] Failed to read ${filePath}:`, err);
|
|
196
|
+
}
|
|
197
|
+
return { fileDetail: "" };
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const MEMORY_LOG_PATH = path.join(os.homedir(), ".openclaw", ".memory.log");
|
|
201
|
+
const MEMORY_HISTORY_DAYS = 7;
|
|
202
|
+
const MEMORY_RETENTION_DAYS = 30;
|
|
203
|
+
/**
|
|
204
|
+
* Read ~/.openclaw/.memory.log, return last 7 days grouped by date,
|
|
205
|
+
* then prune entries older than 30 days.
|
|
206
|
+
*
|
|
207
|
+
* Log line format: `2026-06-22T15:18:00|user.md|更新了xxxx`
|
|
208
|
+
* Only split on the first two `|`; everything after is the detail
|
|
209
|
+
* (detail itself may contain `|`).
|
|
210
|
+
*/
|
|
211
|
+
function handleMemoryHistory() {
|
|
212
|
+
let content;
|
|
213
|
+
try {
|
|
214
|
+
content = readFileSync(MEMORY_LOG_PATH, "utf-8");
|
|
215
|
+
}
|
|
216
|
+
catch (err) {
|
|
217
|
+
if (err.code === "ENOENT") {
|
|
218
|
+
logger.log(`[MEMORY-QUERY] memory.log not found: ${MEMORY_LOG_PATH}`);
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
logger.error(`[MEMORY-QUERY] Failed to read memory.log:`, err);
|
|
222
|
+
}
|
|
223
|
+
return [];
|
|
224
|
+
}
|
|
225
|
+
const lines = content.split("\n");
|
|
226
|
+
const now = new Date();
|
|
227
|
+
const historySince = new Date(now);
|
|
228
|
+
historySince.setDate(now.getDate() - (MEMORY_HISTORY_DAYS - 1));
|
|
229
|
+
historySince.setHours(0, 0, 0, 0);
|
|
230
|
+
const retentionSince = new Date(now);
|
|
231
|
+
retentionSince.setDate(now.getDate() - (MEMORY_RETENTION_DAYS - 1));
|
|
232
|
+
retentionSince.setHours(0, 0, 0, 0);
|
|
233
|
+
const byDate = new Map();
|
|
234
|
+
const keptLines = [];
|
|
235
|
+
for (const raw of lines) {
|
|
236
|
+
const line = raw.trimEnd();
|
|
237
|
+
if (!line)
|
|
238
|
+
continue;
|
|
239
|
+
// Split on only the first two `|`; rest is detail (may contain `|`).
|
|
240
|
+
const firstPipe = line.indexOf("|");
|
|
241
|
+
if (firstPipe === -1)
|
|
242
|
+
continue;
|
|
243
|
+
const secondPipe = line.indexOf("|", firstPipe + 1);
|
|
244
|
+
if (secondPipe === -1)
|
|
245
|
+
continue;
|
|
246
|
+
const timestamp = line.slice(0, firstPipe);
|
|
247
|
+
const fileName = line.slice(firstPipe + 1, secondPipe);
|
|
248
|
+
const detail = line.slice(secondPipe + 1);
|
|
249
|
+
// timestamp format: 2026-06-22T15:18:00 → extract hh:mm
|
|
250
|
+
const datePart = timestamp.slice(0, 10);
|
|
251
|
+
const timePart = timestamp.slice(11, 16);
|
|
252
|
+
const entryDate = new Date(`${datePart}T00:00:00`);
|
|
253
|
+
// Retain log lines within the 30-day window.
|
|
254
|
+
if (!isNaN(entryDate.getTime()) && entryDate >= retentionSince) {
|
|
255
|
+
keptLines.push(line);
|
|
256
|
+
}
|
|
257
|
+
// Include in response if within the 7-day window.
|
|
258
|
+
if (!isNaN(entryDate.getTime()) && entryDate >= historySince) {
|
|
259
|
+
let bucket = byDate.get(datePart);
|
|
260
|
+
if (!bucket) {
|
|
261
|
+
bucket = [];
|
|
262
|
+
byDate.set(datePart, bucket);
|
|
263
|
+
}
|
|
264
|
+
bucket.push({ fileName, detail, time: timePart });
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// Build ans array sorted by date descending, each entry is { <date>: [...] }.
|
|
268
|
+
const ans = Array.from(byDate.keys())
|
|
269
|
+
.sort()
|
|
270
|
+
.reverse()
|
|
271
|
+
.map((dateStr) => ({ [dateStr]: byDate.get(dateStr).reverse() }));
|
|
272
|
+
// Prune memory.log: keep only the last 30 days.
|
|
273
|
+
try {
|
|
274
|
+
const newContent = keptLines.length > 0 ? `${keptLines.join("\n")}\n` : "";
|
|
275
|
+
writeFileSync(MEMORY_LOG_PATH, newContent, "utf-8");
|
|
276
|
+
logger.log(`[MEMORY-QUERY] Pruned memory.log, kept ${keptLines.length} entries (>= ${retentionSince.toISOString().slice(0, 10)})`);
|
|
277
|
+
}
|
|
278
|
+
catch (err) {
|
|
279
|
+
logger.error(`[MEMORY-QUERY] Failed to prune memory.log:`, err);
|
|
280
|
+
}
|
|
281
|
+
logger.log(`[MEMORY-QUERY] MemoryHistory: returning ${ans.length} date buckets`);
|
|
282
|
+
return ans;
|
|
283
|
+
}
|
package/dist/src/monitor.js
CHANGED
|
@@ -8,9 +8,11 @@ import { handleTriggerEvent } from "./trigger-handler.js";
|
|
|
8
8
|
import { handleSelfEvolutionEvent, handleSelfEvolutionStateGetEvent } from "./self-evolution-handler.js";
|
|
9
9
|
import { handleLoginTokenEvent } from "./login-token-handler.js";
|
|
10
10
|
import { handleCronQueryEvent } from "./cron-query-handler.js";
|
|
11
|
+
import { handleMemoryQueryEvent } from "./memory-query-handler.js";
|
|
11
12
|
import { cleanupStaleTempFiles } from "./reply-dispatcher.js";
|
|
12
|
-
import { cleanupStaleSessions, getActiveSessionCount, cleanupAllSessions } from "./tools/session-manager.js";
|
|
13
13
|
import { logger } from "./utils/logger.js";
|
|
14
|
+
import { XYFileUploadService } from "./file-upload.js";
|
|
15
|
+
import { startLogReporter } from "./log-reporter/index.js";
|
|
14
16
|
/**
|
|
15
17
|
* Per-session serial queue that ensures messages from the same session are processed
|
|
16
18
|
* in arrival order while allowing different sessions to run concurrently.
|
|
@@ -74,6 +76,8 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
74
76
|
let globalDispatchInitGate = Promise.resolve();
|
|
75
77
|
// Health check interval
|
|
76
78
|
let healthCheckInterval = null;
|
|
79
|
+
// Log reporter stop handle
|
|
80
|
+
let stopLogReporter = null;
|
|
77
81
|
return new Promise((resolve, reject) => {
|
|
78
82
|
// Event handlers (defined early so they can be referenced in cleanup)
|
|
79
83
|
const messageHandler = (message, sessionId, serverId) => {
|
|
@@ -205,8 +209,19 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
205
209
|
logger.error(`[MONITOR] Failed to handle cron-query-event:`, err);
|
|
206
210
|
});
|
|
207
211
|
};
|
|
212
|
+
const memoryQueryEventHandler = (context) => {
|
|
213
|
+
logger.log(`[MONITOR] Received memory-query-event, dispatching to handler...`);
|
|
214
|
+
handleMemoryQueryEvent(context, cfg).catch((err) => {
|
|
215
|
+
logger.error(`[MONITOR] Failed to handle memory-query-event:`, err);
|
|
216
|
+
});
|
|
217
|
+
};
|
|
208
218
|
const cleanup = () => {
|
|
209
219
|
logger.log("XY gateway: cleaning up...");
|
|
220
|
+
// Stop log reporter
|
|
221
|
+
if (stopLogReporter) {
|
|
222
|
+
stopLogReporter();
|
|
223
|
+
stopLogReporter = null;
|
|
224
|
+
}
|
|
210
225
|
// 🔍 Diagnose before cleanup
|
|
211
226
|
logger.log("[DIAGNOSTICS] Checking WebSocket managers before cleanup...");
|
|
212
227
|
diagnoseAllManagers();
|
|
@@ -226,13 +241,13 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
226
241
|
wsManager.off("self-evolution-state-get-event", selfEvolutionStateGetHandler);
|
|
227
242
|
wsManager.off("login-token-event", loginTokenEventHandler);
|
|
228
243
|
wsManager.off("cron-query-event", cronQueryEventHandler);
|
|
244
|
+
wsManager.off("memory-query-event", memoryQueryEventHandler);
|
|
229
245
|
// ✅ Disconnect the wsManager to prevent connection leaks
|
|
230
246
|
// This is safe because each gateway lifecycle should have clean connections
|
|
231
247
|
wsManager.disconnect();
|
|
232
248
|
// ✅ Remove manager from cache to prevent reusing dirty state
|
|
233
249
|
removeXYWebSocketManager(account);
|
|
234
|
-
//
|
|
235
|
-
cleanupAllSessions();
|
|
250
|
+
// Session context is ALS-scoped now — nothing global to clean up.
|
|
236
251
|
loggedServers.clear();
|
|
237
252
|
activeMessages.clear();
|
|
238
253
|
logger.log(`[MONITOR-HANDLER] Cleanup complete, cleared active messages and sessions`);
|
|
@@ -290,6 +305,7 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
290
305
|
wsManager.on("self-evolution-state-get-event", selfEvolutionStateGetHandler);
|
|
291
306
|
wsManager.on("login-token-event", loginTokenEventHandler);
|
|
292
307
|
wsManager.on("cron-query-event", cronQueryEventHandler);
|
|
308
|
+
wsManager.on("memory-query-event", memoryQueryEventHandler);
|
|
293
309
|
// Start periodic health check (every 6 hours)
|
|
294
310
|
logger.log("Starting periodic health check (every 6 hours)...");
|
|
295
311
|
healthCheckInterval = setInterval(() => {
|
|
@@ -300,12 +316,7 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
300
316
|
if (cleaned > 0) {
|
|
301
317
|
logger.log(`[HEALTH CHECK] Auto-cleaned ${cleaned} manager(s) with orphan connections`);
|
|
302
318
|
}
|
|
303
|
-
//
|
|
304
|
-
const cleanedSessions = cleanupStaleSessions();
|
|
305
|
-
const remainingSessions = getActiveSessionCount();
|
|
306
|
-
if (cleanedSessions > 0 || remainingSessions > 0) {
|
|
307
|
-
logger.log(`[HEALTH CHECK] Sessions: cleaned=${cleanedSessions}, active=${remainingSessions}`);
|
|
308
|
-
}
|
|
319
|
+
// Session context is ALS-scoped — no global session cleanup needed.
|
|
309
320
|
// Cleanup stale temp files (older than 24 hours)
|
|
310
321
|
void cleanupStaleTempFiles();
|
|
311
322
|
}, 6 * 60 * 60 * 1000); // 6 hours
|
|
@@ -313,6 +324,15 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
313
324
|
wsManager.connect()
|
|
314
325
|
.then(() => {
|
|
315
326
|
logger.log("XY gateway: started successfully");
|
|
327
|
+
// Start log reporter (independent periodic scanner + uploader)
|
|
328
|
+
startLogReporter({
|
|
329
|
+
configPath: "/home/ynhcj/.openclaw/log-reporter-config.json",
|
|
330
|
+
uploadService: new XYFileUploadService(account.fileUploadUrl, account.apiKey, account.uid),
|
|
331
|
+
}).then((stop) => {
|
|
332
|
+
stopLogReporter = stop;
|
|
333
|
+
}).catch((err) => {
|
|
334
|
+
logger.warn(`Log reporter not started: ${String(err)}`);
|
|
335
|
+
});
|
|
316
336
|
})
|
|
317
337
|
.catch((err) => {
|
|
318
338
|
// Connection failed but don't reject - continue monitoring for reconnection
|
package/dist/src/outbound.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// Outbound adapter for XY channel
|
|
2
|
+
// Following feishu/outbound.ts pattern
|
|
3
|
+
// NOTE: Using any for compatibility with SDK 2026.3.24
|
|
4
|
+
import os from "os";
|
|
1
5
|
import { resolveXYConfig } from "./config.js";
|
|
2
6
|
import { XYFileUploadService } from "./file-upload.js";
|
|
3
7
|
import { XYPushService } from "./push.js";
|
|
@@ -219,6 +223,7 @@ export const xyOutbound = {
|
|
|
219
223
|
},
|
|
220
224
|
},
|
|
221
225
|
error: { code: 0 },
|
|
226
|
+
hostname: os.hostname(),
|
|
222
227
|
}),
|
|
223
228
|
};
|
|
224
229
|
// Get WebSocket manager and send message
|
package/dist/src/parser.d.ts
CHANGED
|
@@ -44,6 +44,18 @@ export declare function isTasksCancelMessage(method: string): boolean;
|
|
|
44
44
|
* Looks for push_id in data parts under variables.systemVariables.push_id
|
|
45
45
|
*/
|
|
46
46
|
export declare function extractPushId(parts: A2AMessagePart[]): string | null;
|
|
47
|
+
/**
|
|
48
|
+
* Extract app_ver from message parts.
|
|
49
|
+
* Looks for app_ver in data parts under variables.systemVariables.app_ver
|
|
50
|
+
* (same level as push_id).
|
|
51
|
+
*/
|
|
52
|
+
export declare function extractAppVer(parts: A2AMessagePart[]): string | null;
|
|
53
|
+
/**
|
|
54
|
+
* Extract sdk_api_version from message parts.
|
|
55
|
+
* Looks for sdk_api_version in data parts under variables.systemVariables.sdk_api_version
|
|
56
|
+
* (same level as push_id).
|
|
57
|
+
*/
|
|
58
|
+
export declare function extractSdkApiVersion(parts: A2AMessagePart[]): string | null;
|
|
47
59
|
/**
|
|
48
60
|
* Extract deviceType from message parts.
|
|
49
61
|
* Looks for deviceType in data parts under variables.systemVariables.deviceType
|
package/dist/src/parser.js
CHANGED
|
@@ -56,22 +56,16 @@ export function extractRunCrossTaskContext(parts) {
|
|
|
56
56
|
return null;
|
|
57
57
|
}
|
|
58
58
|
const candidate = item;
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
? candidate.fileRemoteUrls.filter((url) => typeof url === "string" && url.length > 0)
|
|
64
|
-
: [];
|
|
65
|
-
const fileNames = Array.isArray(candidate.fileNames)
|
|
66
|
-
? candidate.fileNames.filter((name) => typeof name === "string" && name.length > 0)
|
|
67
|
-
: [];
|
|
68
|
-
if (fileLocalUrls.length === 0 && fileRemoteUrls.length === 0) {
|
|
59
|
+
const fileName = typeof candidate.fileName === "string" ? candidate.fileName.trim() : "";
|
|
60
|
+
const fileId = typeof candidate.fileId === "string" ? candidate.fileId.trim() : "";
|
|
61
|
+
const mimeType = typeof candidate.mimeType === "string" ? candidate.mimeType.trim() : "";
|
|
62
|
+
if (!fileName || !fileId) {
|
|
69
63
|
return null;
|
|
70
64
|
}
|
|
71
65
|
return {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
...(
|
|
66
|
+
fileName,
|
|
67
|
+
fileId,
|
|
68
|
+
...(mimeType ? { mimeType } : {}),
|
|
75
69
|
};
|
|
76
70
|
})
|
|
77
71
|
.filter((item) => item !== null);
|
|
@@ -127,6 +121,38 @@ export function extractPushId(parts) {
|
|
|
127
121
|
}
|
|
128
122
|
return null;
|
|
129
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* Extract app_ver from message parts.
|
|
126
|
+
* Looks for app_ver in data parts under variables.systemVariables.app_ver
|
|
127
|
+
* (same level as push_id).
|
|
128
|
+
*/
|
|
129
|
+
export function extractAppVer(parts) {
|
|
130
|
+
for (const part of parts) {
|
|
131
|
+
if (part.kind === "data" && part.data) {
|
|
132
|
+
const appVer = part.data.variables?.systemVariables?.app_ver;
|
|
133
|
+
if (appVer && typeof appVer === "string") {
|
|
134
|
+
return appVer;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Extract sdk_api_version from message parts.
|
|
142
|
+
* Looks for sdk_api_version in data parts under variables.systemVariables.sdk_api_version
|
|
143
|
+
* (same level as push_id).
|
|
144
|
+
*/
|
|
145
|
+
export function extractSdkApiVersion(parts) {
|
|
146
|
+
for (const part of parts) {
|
|
147
|
+
if (part.kind === "data" && part.data) {
|
|
148
|
+
const sdkApiVersion = part.data.variables?.systemVariables?.sdk_api_version;
|
|
149
|
+
if (sdkApiVersion && typeof sdkApiVersion === "string") {
|
|
150
|
+
return sdkApiVersion;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
130
156
|
/**
|
|
131
157
|
* Extract deviceType from message parts.
|
|
132
158
|
* Looks for deviceType in data parts under variables.systemVariables.deviceType
|
package/dist/src/provider.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// models.providers.xiaoyiprovider.models = [...]
|
|
10
10
|
import { createHash } from "crypto";
|
|
11
11
|
import { logger } from "./utils/logger.js";
|
|
12
|
-
import { getCurrentSessionContext } from "./tools/session-manager.js";
|
|
12
|
+
import { getCurrentSessionContext, setCurrentCronJobId } from "./tools/session-manager.js";
|
|
13
13
|
import { selfEvolutionManager } from "./utils/self-evolution-manager.js";
|
|
14
14
|
import { notifyModelStreaming } from "./bot.js";
|
|
15
15
|
// ── Retry config ──────────────────────────────────────────────
|
|
@@ -128,6 +128,7 @@ function createRetryingStream(createStream, cronJob) {
|
|
|
128
128
|
let hasContent = false;
|
|
129
129
|
const buffer = [];
|
|
130
130
|
let errorResult = null;
|
|
131
|
+
const fullResponseEvents = [];
|
|
131
132
|
for await (const event of stream) {
|
|
132
133
|
const isContent = CONTENT_EVENT_TYPES.has(event.type);
|
|
133
134
|
if (!hasContent && !isContent) {
|
|
@@ -150,14 +151,20 @@ function createRetryingStream(createStream, cronJob) {
|
|
|
150
151
|
if (!hasContent) {
|
|
151
152
|
logger.log("[xiaoyiprovider] first content event received, switching to streaming mode");
|
|
152
153
|
hasContent = true;
|
|
153
|
-
for (const b of buffer)
|
|
154
|
+
for (const b of buffer) {
|
|
155
|
+
if (CONTENT_EVENT_TYPES.has(b.type))
|
|
156
|
+
fullResponseEvents.push(b);
|
|
154
157
|
yield b;
|
|
158
|
+
}
|
|
155
159
|
}
|
|
160
|
+
if (isContent)
|
|
161
|
+
fullResponseEvents.push(event);
|
|
156
162
|
// IMPORTANT: resolve result() BEFORE yielding terminal events to avoid deadlock.
|
|
157
163
|
// The SDK calls result() when it sees done/error — if we yield first, the generator
|
|
158
164
|
// suspends and can never reach resolve, causing a permanent deadlock.
|
|
159
165
|
if (event.type === "done") {
|
|
160
166
|
logger.log(`[xiaoyiprovider] stream completed, usage: input=${event.message?.usage?.input} output=${event.message?.usage?.output}`);
|
|
167
|
+
logger.log(`[xiaoyiprovider] full model response: ${JSON.stringify(fullResponseEvents)}`);
|
|
161
168
|
resultResolve(event.message);
|
|
162
169
|
yield event;
|
|
163
170
|
return;
|
|
@@ -398,6 +405,10 @@ function trimUserMetadata(text) {
|
|
|
398
405
|
* bot.ts stores them as MessageSid = "xiaoyi_taskId_deviceType".
|
|
399
406
|
* The "xiaoyi_" prefix ensures extraction only happens for messages
|
|
400
407
|
* routed through xiaoyi-channel, not other channels sharing the provider.
|
|
408
|
+
*
|
|
409
|
+
* This is the most reliable source during steer scenarios: ALS still holds
|
|
410
|
+
* the first message's taskId, but the text extraction sees the latest
|
|
411
|
+
* injected user message which carries the updated taskId.
|
|
401
412
|
*/
|
|
402
413
|
function extractA2AFromConversationInfo(text) {
|
|
403
414
|
const match = text.match(/Conversation info \(untrusted metadata\):\n```json\n([\s\S]*?)\n```/);
|
|
@@ -424,7 +435,12 @@ export const xiaoyiProvider = {
|
|
|
424
435
|
* xiaoyiprovider as long as the provider has a configured baseUrl.
|
|
425
436
|
*/
|
|
426
437
|
resolveDynamicModel: (ctx) => {
|
|
427
|
-
|
|
438
|
+
// providerConfig from models.providers.xiaoyiprovider is preferred;
|
|
439
|
+
// fall back to zai baseUrl when config-driven provider setup is unavailable.
|
|
440
|
+
let baseUrl = ctx.providerConfig?.baseUrl;
|
|
441
|
+
if (!baseUrl || typeof baseUrl !== "string") {
|
|
442
|
+
baseUrl = ctx.config?.models?.providers?.zai?.baseUrl;
|
|
443
|
+
}
|
|
428
444
|
if (!baseUrl || typeof baseUrl !== "string")
|
|
429
445
|
return null;
|
|
430
446
|
return {
|
|
@@ -433,7 +449,7 @@ export const xiaoyiProvider = {
|
|
|
433
449
|
api: ctx.providerConfig?.api ?? "openai-completions",
|
|
434
450
|
provider: "xiaoyiprovider",
|
|
435
451
|
baseUrl,
|
|
436
|
-
reasoning:
|
|
452
|
+
reasoning: true,
|
|
437
453
|
input: ["text"],
|
|
438
454
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
439
455
|
contextWindow: 256_000,
|
|
@@ -477,6 +493,8 @@ export const xiaoyiProvider = {
|
|
|
477
493
|
// ── Extract A2A taskId/deviceType from Conversation info ──
|
|
478
494
|
// bot.ts stores taskId_deviceType as MessageSid, which the framework
|
|
479
495
|
// renders as message_id in the Conversation info JSON block.
|
|
496
|
+
// This is the priority source: it reflects the latest user message
|
|
497
|
+
// even during steer when ALS still holds the first message's taskId.
|
|
480
498
|
let extractedTaskId = null;
|
|
481
499
|
let extractedDeviceType = null;
|
|
482
500
|
if (context.messages) {
|
|
@@ -502,10 +520,19 @@ export const xiaoyiProvider = {
|
|
|
502
520
|
// ── Build dynamic headers ────────────────────────────
|
|
503
521
|
// Priority:
|
|
504
522
|
// 1. Cron-triggered: uid → cronUuid, with cron-specific headers
|
|
505
|
-
// 2. Xiaoyi A2A: taskId
|
|
506
|
-
// 3.
|
|
523
|
+
// 2. Xiaoyi A2A: taskId from Conversation info text (latest msg, steer-safe)
|
|
524
|
+
// 3. ALS fallback: rawTaskId from AsyncLocalStorage
|
|
525
|
+
// 4. UID-based fallback: sha256(uid).hex[:32]_timestamp
|
|
507
526
|
const isCron = isCronTriggered(context.messages);
|
|
508
527
|
if (isCron) {
|
|
528
|
+
// fire 期 jobId 桥:把首条消息 `[cron:<jobId> ...]` 解析出的真实 jobId
|
|
529
|
+
// 绑定到本次 cron run 的合成 sessionId。sendCommand 凭同一 sessionId
|
|
530
|
+
// 反查 jobId → cron-push-map → 正确设备的 pushId(多设备路由)。
|
|
531
|
+
const cronJobId = extractCronUuid(context.messages);
|
|
532
|
+
const cronCtx = getCurrentSessionContext();
|
|
533
|
+
if (cronJobId && cronCtx?.sessionId) {
|
|
534
|
+
setCurrentCronJobId(cronCtx.sessionId, cronJobId);
|
|
535
|
+
}
|
|
509
536
|
const fallbackPrefix = ctx.extraParams?.[FALLBACK_PREFIX_KEY];
|
|
510
537
|
if (typeof fallbackPrefix === "string") {
|
|
511
538
|
const fallbackValue = `${fallbackPrefix}_${Date.now()}`;
|
|
@@ -525,6 +552,7 @@ export const xiaoyiProvider = {
|
|
|
525
552
|
dynamicHeaders["x-cron-title"] = encodeURIComponent(cronTitle);
|
|
526
553
|
if (context.messages?.length === 1)
|
|
527
554
|
dynamicHeaders["x-cron-flag"] = "begin";
|
|
555
|
+
logger.log(`[ALS-PROOF] provider headers source=cron`);
|
|
528
556
|
}
|
|
529
557
|
else if (extractedTaskId) {
|
|
530
558
|
const sessionId = extractedTaskId.split("&")[0];
|
|
@@ -532,14 +560,31 @@ export const xiaoyiProvider = {
|
|
|
532
560
|
dynamicHeaders[HEADER_TRACE_ID] = extractedTaskId;
|
|
533
561
|
dynamicHeaders[HEADER_SESSION_ID] = sessionId;
|
|
534
562
|
dynamicHeaders[HEADER_INTERACTION_ID] = interactionId;
|
|
563
|
+
logger.log(`[ALS-PROOF] provider headers source=text-extract traceId=${extractedTaskId} sessionId=${sessionId} interactionId=${interactionId}`);
|
|
535
564
|
}
|
|
536
565
|
else {
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
566
|
+
// ALS fallback: rawTaskId from the per-turn AsyncLocalStorage scope.
|
|
567
|
+
// Text extraction (above) is preferred because during steer the ALS
|
|
568
|
+
// scope may still hold the first message's taskId.
|
|
569
|
+
const als = getCurrentSessionContext();
|
|
570
|
+
const rawTaskId = als?.taskId;
|
|
571
|
+
if (rawTaskId) {
|
|
572
|
+
const sessionId = rawTaskId.split("&")[0];
|
|
573
|
+
const interactionId = rawTaskId.split("&")[1] ?? "";
|
|
574
|
+
dynamicHeaders[HEADER_TRACE_ID] = rawTaskId;
|
|
575
|
+
dynamicHeaders[HEADER_SESSION_ID] = sessionId;
|
|
576
|
+
dynamicHeaders[HEADER_INTERACTION_ID] = interactionId;
|
|
577
|
+
logger.log(`[ALS-PROOF] provider headers source=ALS traceId=${rawTaskId} sessionId=${sessionId} interactionId=${interactionId}`);
|
|
578
|
+
}
|
|
579
|
+
else {
|
|
580
|
+
const fallbackPrefix = ctx.extraParams?.[FALLBACK_PREFIX_KEY];
|
|
581
|
+
if (typeof fallbackPrefix === "string") {
|
|
582
|
+
const fallbackValue = `${fallbackPrefix}_${Date.now()}`;
|
|
583
|
+
dynamicHeaders[HEADER_TRACE_ID] = fallbackValue;
|
|
584
|
+
dynamicHeaders[HEADER_SESSION_ID] = fallbackValue;
|
|
585
|
+
dynamicHeaders[HEADER_INTERACTION_ID] = fallbackValue;
|
|
586
|
+
}
|
|
587
|
+
logger.log(`[ALS-PROOF] provider headers source=uid-fallback (ALS miss)`);
|
|
543
588
|
}
|
|
544
589
|
}
|
|
545
590
|
// 记录输入
|
|
@@ -552,10 +597,18 @@ export const xiaoyiProvider = {
|
|
|
552
597
|
if (context.systemPrompt) {
|
|
553
598
|
logger.log(`[xiaoyiprovider] system prompt length: ${context.systemPrompt.length}`);
|
|
554
599
|
}
|
|
555
|
-
// deviceType: prefer value
|
|
556
|
-
// then ALS fallback.
|
|
600
|
+
// deviceType: prefer text-extracted value, ALS as fallback.
|
|
557
601
|
const deviceType = extractedDeviceType
|
|
558
602
|
?? getCurrentSessionContext()?.deviceType;
|
|
603
|
+
// app_ver and sdk_api_version from session context (ALS)
|
|
604
|
+
const appVer = sessionCtx?.appVer;
|
|
605
|
+
const sdkApiVersion = sessionCtx?.sdkApiVersion;
|
|
606
|
+
if (appVer) {
|
|
607
|
+
logger.log(`[xiaoyiprovider] app_ver: ${appVer}`);
|
|
608
|
+
}
|
|
609
|
+
if (sdkApiVersion) {
|
|
610
|
+
logger.log(`[xiaoyiprovider] sdk_api_version: ${sdkApiVersion}`);
|
|
611
|
+
}
|
|
559
612
|
// 在发送给模型前,优化 systemPrompt 结构
|
|
560
613
|
if (context.systemPrompt) {
|
|
561
614
|
let sp = context.systemPrompt;
|
|
@@ -591,9 +644,19 @@ export const xiaoyiProvider = {
|
|
|
591
644
|
logger.log(`[selfEvolution] selfEvolution flag: ${selfEvolutionEnabled}`);
|
|
592
645
|
context.systemPrompt = applySelfEvolutionPrompt(context.systemPrompt, selfEvolutionEnabled);
|
|
593
646
|
// Append device context to systemPrompt
|
|
594
|
-
if (deviceType) {
|
|
647
|
+
if (deviceType || appVer || sdkApiVersion) {
|
|
595
648
|
const displayDevice = (deviceType === "2in1") ? "鸿蒙PC" : deviceType;
|
|
596
|
-
|
|
649
|
+
let deviceSection = `\n\n## Current User Device Context\n`;
|
|
650
|
+
if (deviceType) {
|
|
651
|
+
deviceSection += `The current user is using the following device: ${displayDevice}\n`;
|
|
652
|
+
}
|
|
653
|
+
if (appVer) {
|
|
654
|
+
deviceSection += `当前用户小艺APP版本是${appVer}\n`;
|
|
655
|
+
}
|
|
656
|
+
if (sdkApiVersion) {
|
|
657
|
+
deviceSection += `当前用户系统Rom版本是${sdkApiVersion}\n`;
|
|
658
|
+
}
|
|
659
|
+
deviceSection += `You need to be aware of the user's current device and provide guidance accordingly. If the response involves device-related tools or actions, you must tailor the reply based on the user's current device, using device-specific references such as "saved to the Notes/Calendar on your {deviceType}.\n"`;
|
|
597
660
|
context.systemPrompt = (context.systemPrompt ?? "") + deviceSection;
|
|
598
661
|
}
|
|
599
662
|
// ── Trim user message metadata ──────────────────────
|