clawconnect-agent 0.2.6 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/README.zh-CN.md +7 -3
- package/dist/commands/hermes-file-transfer-outcome.d.ts +18 -0
- package/dist/commands/hermes-file-transfer-outcome.js +40 -0
- package/dist/commands/hermes-file-transfer-outcome.js.map +1 -0
- package/dist/commands/send-file.js +39 -33
- package/dist/commands/send-file.js.map +1 -1
- package/dist/core/command-types.d.ts +5 -0
- package/dist/core/relay/chat-payload.js +32 -22
- package/dist/core/relay/chat-payload.js.map +1 -1
- package/dist/core/relay/file-upload.d.ts +1 -0
- package/dist/core/relay/file-upload.js +3 -0
- package/dist/core/relay/file-upload.js.map +1 -1
- package/dist/core/relay/mobile-chat-run-bridge.d.ts +53 -8
- package/dist/core/relay/mobile-chat-run-bridge.js +45 -0
- package/dist/core/relay/mobile-chat-run-bridge.js.map +1 -1
- package/dist/core/relay/office-payload.js +54 -22
- package/dist/core/relay/office-payload.js.map +1 -1
- package/dist/core/relay/relay-server-connection.d.ts +1 -0
- package/dist/core/relay/relay-server-connection.js +17 -0
- package/dist/core/relay/relay-server-connection.js.map +1 -1
- package/dist/core/relay/source-commit.d.ts +42 -0
- package/dist/core/relay/source-commit.js +127 -0
- package/dist/core/relay/source-commit.js.map +1 -0
- package/dist/core/relay/timeline-event-builder.d.ts +6 -0
- package/dist/core/relay/timeline-event-builder.js +3 -0
- package/dist/core/relay/timeline-event-builder.js.map +1 -1
- package/dist/core/relay/timeline-event-log.d.ts +29 -0
- package/dist/core/relay/timeline-event-log.js +37 -0
- package/dist/core/relay/timeline-event-log.js.map +1 -1
- package/dist/core/relay/timeline-projection-v3.d.ts +63 -0
- package/dist/core/relay/timeline-projection-v3.js +87 -0
- package/dist/core/relay/timeline-projection-v3.js.map +1 -0
- package/dist/hermes/hermes-relay-manager.js +3 -0
- package/dist/hermes/hermes-relay-manager.js.map +1 -1
- package/dist/hermes/hermes-runtime-test-support.d.ts +3 -3
- package/dist/hermes/hermes-runtime-test-support.js +12 -6
- package/dist/hermes/hermes-runtime-test-support.js.map +1 -1
- package/dist/hermes/runtime/hermes-file-transfer-state.d.ts +33 -0
- package/dist/hermes/runtime/hermes-file-transfer-state.js +139 -0
- package/dist/hermes/runtime/hermes-file-transfer-state.js.map +1 -0
- package/dist/hermes/runtime/hermes-mobile-file-route-store.d.ts +17 -0
- package/dist/hermes/runtime/hermes-mobile-file-route-store.js +103 -0
- package/dist/hermes/runtime/hermes-mobile-file-route-store.js.map +1 -0
- package/dist/hermes/runtime/hermes-runtime-api-client.d.ts +1 -0
- package/dist/hermes/runtime/hermes-runtime-api-client.js +16 -0
- package/dist/hermes/runtime/hermes-runtime-api-client.js.map +1 -1
- package/dist/hermes/runtime/hermes-runtime-chat.d.ts +35 -4
- package/dist/hermes/runtime/hermes-runtime-chat.js +426 -51
- package/dist/hermes/runtime/hermes-runtime-chat.js.map +1 -1
- package/dist/hermes/runtime/hermes-runtime-command-router.js +9 -1
- package/dist/hermes/runtime/hermes-runtime-command-router.js.map +1 -1
- package/dist/hermes/runtime/hermes-runtime-cron.d.ts +1 -0
- package/dist/hermes/runtime/hermes-runtime-cron.js +34 -66
- package/dist/hermes/runtime/hermes-runtime-cron.js.map +1 -1
- package/dist/hermes/runtime/hermes-runtime-history-completion.d.ts +8 -1
- package/dist/hermes/runtime/hermes-runtime-history-completion.js +112 -41
- package/dist/hermes/runtime/hermes-runtime-history-completion.js.map +1 -1
- package/dist/hermes/runtime/hermes-runtime-history.js +92 -17
- package/dist/hermes/runtime/hermes-runtime-history.js.map +1 -1
- package/dist/hermes/runtime/hermes-runtime-models.js +4 -1
- package/dist/hermes/runtime/hermes-runtime-models.js.map +1 -1
- package/dist/hermes/runtime/hermes-runtime-process.js +4 -3
- package/dist/hermes/runtime/hermes-runtime-process.js.map +1 -1
- package/dist/hermes/runtime/hermes-runtime-state-db.d.ts +6 -0
- package/dist/hermes/runtime/hermes-runtime-state-db.js +20 -1
- package/dist/hermes/runtime/hermes-runtime-state-db.js.map +1 -1
- package/dist/hermes/runtime/hermes-runtime-tool-log-watcher.js +12 -20
- package/dist/hermes/runtime/hermes-runtime-tool-log-watcher.js.map +1 -1
- package/dist/index.js +16 -2
- package/dist/index.js.map +1 -1
- package/dist/openclaw/config/provider-config.js +4 -1
- package/dist/openclaw/config/provider-config.js.map +1 -1
- package/dist/openclaw/relay/agent-identity-helpers.d.ts +9 -0
- package/dist/openclaw/relay/agent-identity-helpers.js +118 -0
- package/dist/openclaw/relay/agent-identity-helpers.js.map +1 -0
- package/dist/openclaw/relay/assistant-media-sidecar.d.ts +19 -2
- package/dist/openclaw/relay/assistant-media-sidecar.js +131 -15
- package/dist/openclaw/relay/assistant-media-sidecar.js.map +1 -1
- package/dist/openclaw/relay/chat-history.d.ts +37 -2
- package/dist/openclaw/relay/chat-history.js +565 -51
- package/dist/openclaw/relay/chat-history.js.map +1 -1
- package/dist/openclaw/relay/chat-run-identity.js +3 -1
- package/dist/openclaw/relay/chat-run-identity.js.map +1 -1
- package/dist/openclaw/relay/gateway-history-projection.d.ts +4 -0
- package/dist/openclaw/relay/gateway-history-projection.js +67 -0
- package/dist/openclaw/relay/gateway-history-projection.js.map +1 -0
- package/dist/openclaw/relay/openclaw-active-run-state.d.ts +67 -0
- package/dist/openclaw/relay/openclaw-active-run-state.js +283 -0
- package/dist/openclaw/relay/openclaw-active-run-state.js.map +1 -0
- package/dist/openclaw/relay/openclaw-message-tool-real-shape.fixture.d.ts +12 -0
- package/dist/openclaw/relay/openclaw-message-tool-real-shape.fixture.js +48 -0
- package/dist/openclaw/relay/openclaw-message-tool-real-shape.fixture.js.map +1 -0
- package/dist/openclaw/relay/openclaw-source-commit-watcher.d.ts +23 -0
- package/dist/openclaw/relay/openclaw-source-commit-watcher.js +65 -0
- package/dist/openclaw/relay/openclaw-source-commit-watcher.js.map +1 -0
- package/dist/openclaw/relay/outgoing-media-relay.d.ts +30 -3
- package/dist/openclaw/relay/outgoing-media-relay.js +407 -118
- package/dist/openclaw/relay/outgoing-media-relay.js.map +1 -1
- package/dist/openclaw/relay/relay-manager-history-timing.d.ts +1 -4
- package/dist/openclaw/relay/relay-manager-history-timing.js +4 -4
- package/dist/openclaw/relay/relay-manager-history-timing.js.map +1 -1
- package/dist/openclaw/relay/relay-manager-payload-helpers.d.ts +28 -1
- package/dist/openclaw/relay/relay-manager-payload-helpers.js +219 -11
- package/dist/openclaw/relay/relay-manager-payload-helpers.js.map +1 -1
- package/dist/openclaw/relay/session-context.d.ts +15 -0
- package/dist/openclaw/relay/session-context.js +48 -4
- package/dist/openclaw/relay/session-context.js.map +1 -1
- package/dist/openclaw/relay/source-commit-projection.d.ts +25 -0
- package/dist/openclaw/relay/source-commit-projection.js +140 -0
- package/dist/openclaw/relay/source-commit-projection.js.map +1 -0
- package/dist/openclaw/relay/transcript-dedupe.js +11 -42
- package/dist/openclaw/relay/transcript-dedupe.js.map +1 -1
- package/dist/openclaw/relay-manager.js +328 -136
- package/dist/openclaw/relay-manager.js.map +1 -1
- package/dist/openclaw/runtime/local-runtime.js +4 -2
- package/dist/openclaw/runtime/local-runtime.js.map +1 -1
- package/package.json +1 -1
- package/dist/openclaw/session-store.d.ts +0 -6
- package/dist/openclaw/session-store.js +0 -231
- package/dist/openclaw/session-store.js.map +0 -1
|
@@ -1,43 +1,274 @@
|
|
|
1
1
|
import { existsSync } from "fs";
|
|
2
|
-
import { readFile, stat } from "fs/promises";
|
|
2
|
+
import { readFile, stat, realpath } from "fs/promises";
|
|
3
3
|
import { homedir } from "os";
|
|
4
4
|
import { isAbsolute, extname, join, relative, resolve } from "path";
|
|
5
5
|
import { DatabaseSync } from "node:sqlite";
|
|
6
6
|
import { createHash } from "crypto";
|
|
7
7
|
import { uploadFileToRelay } from "../../core/relay/file-upload.js";
|
|
8
8
|
import { resolveOpenClawStateDir } from "../runtime/openclaw-paths.js";
|
|
9
|
-
import { normalizeOpenClawAssistantMediaSidecars, normalizeOpenClawAutomaticMediaReplies, } from "./assistant-media-sidecar.js";
|
|
9
|
+
import { extractOpenClawMessageToolRelation, normalizeOpenClawAssistantMediaSidecars, normalizeOpenClawAutomaticMediaReplies, } from "./assistant-media-sidecar.js";
|
|
10
|
+
import { canonicalizeOpenClawSessionScope, canonicalizeSessionKey, } from "./session-context.js";
|
|
10
11
|
const OUTGOING_MEDIA_RE = /\/api\/chat\/media\/outgoing\/[^/]+\/([^/]+)\/full(?:$|[?#])/;
|
|
11
12
|
const OPENCLAW_MEDIA_CONTROL_PREFIX_RE = /^MEDIA:\s*(?:file:\/\/|~[\\/]|\/|[A-Za-z]:[\\/]|\\\\)/i;
|
|
12
13
|
const OPENCLAW_INPUT_MEDIA_MARKER_RE = /\[media attached:\s+(.+?)\s+\(([^)\r\n]+)\)\s+\|\s+(.+?)\]/g;
|
|
14
|
+
const OPENCLAW_DELIVERY_CONTRACT = "openclaw.message-tool-delivery.v1";
|
|
15
|
+
const OUTGOING_MEDIA_RECORD_WAIT_DELAYS_MS = [80, 160, 320, 640];
|
|
13
16
|
const inFlightUploadsByCache = new WeakMap();
|
|
14
17
|
export async function relayOutgoingMediaInPayload(payload, opts) {
|
|
15
18
|
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
16
19
|
return payload;
|
|
17
20
|
}
|
|
18
|
-
const message = payload.message;
|
|
19
|
-
if (!message || typeof message !== "object" || Array.isArray(message)) {
|
|
20
|
-
return payload;
|
|
21
|
-
}
|
|
22
21
|
const payloadRecord = payload;
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
22
|
+
const message = asRecord(payloadRecord.message);
|
|
23
|
+
const messageRelation = message ? extractOpenClawMessageToolRelation(message) : undefined;
|
|
24
|
+
const sourceRunId = messageRelation?.sourceRunId ?? payloadSourceRunId(payloadRecord);
|
|
25
|
+
let nextMessage;
|
|
26
|
+
let messageChanged = false;
|
|
27
|
+
let deliveryBlocks = [];
|
|
28
|
+
if (message) {
|
|
29
|
+
const messageContent = await relayOutgoingMediaContent(message.content, {
|
|
30
|
+
...opts,
|
|
31
|
+
sourceRunId,
|
|
32
|
+
sessionKey: canonicalRelationSessionKey(firstString(message.sessionKey, message.sessionId, payloadRecord.sessionKey), opts),
|
|
33
|
+
...(messageRelation?.toolCallId ? { toolCallId: messageRelation.toolCallId } : {}),
|
|
34
|
+
});
|
|
35
|
+
deliveryBlocks = await relayOpenClawDeliveryMedia(message, payloadRecord, opts, sourceRunId);
|
|
36
|
+
const appendedMessage = appendUniqueContentBlocks(messageContent.blocks, deliveryBlocks);
|
|
37
|
+
const nextMessageContent = appendedMessage.changed ? appendedMessage.blocks : messageContent.content;
|
|
38
|
+
messageChanged = messageContent.changed || appendedMessage.changed;
|
|
39
|
+
if (messageChanged) {
|
|
40
|
+
nextMessage = {
|
|
41
|
+
...message,
|
|
42
|
+
content: nextMessageContent,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
26
46
|
const timelineEvents = await relayOutgoingMediaInTimelineEvents(payloadRecord.timelineEvents, opts, sourceRunId);
|
|
27
|
-
|
|
47
|
+
const deliveryTimelineEvents = relayOpenClawDeliveryMediaInTimelineEvents(timelineEvents.events, deliveryBlocks, sourceRunId, opts.sessionDefaults);
|
|
48
|
+
if (!messageChanged
|
|
49
|
+
&& !timelineEvents.changed
|
|
50
|
+
&& !deliveryTimelineEvents.changed) {
|
|
28
51
|
return payload;
|
|
29
52
|
}
|
|
30
53
|
return {
|
|
31
54
|
...payloadRecord,
|
|
32
|
-
message: {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
: messageContent.content,
|
|
37
|
-
},
|
|
38
|
-
...(timelineEvents.changed ? { timelineEvents: timelineEvents.events } : {}),
|
|
55
|
+
...(nextMessage ? { message: nextMessage } : {}),
|
|
56
|
+
...(timelineEvents.changed || deliveryTimelineEvents.changed
|
|
57
|
+
? { timelineEvents: deliveryTimelineEvents.events }
|
|
58
|
+
: {}),
|
|
39
59
|
};
|
|
40
60
|
}
|
|
61
|
+
async function relayOpenClawDeliveryMedia(message, payload, opts, sourceRunId) {
|
|
62
|
+
// `mediaUrls` is a typed OpenClaw delivery projection. Its presence is the
|
|
63
|
+
// producer's explicit publication receipt; assistant text and the original
|
|
64
|
+
// user prompt are intentionally not consulted here.
|
|
65
|
+
if (!sourceRunId) {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
const receipt = typedOpenClawDeliveryReceipt(message.openclawDelivery, sourceRunId)
|
|
69
|
+
?? adaptOpenClawMessageToolDelivery({ message, ...payload }, sourceRunId);
|
|
70
|
+
if (!receipt) {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
const paths = trustedDeliverablePaths(receipt.mediaUrls);
|
|
74
|
+
if (paths.length === 0) {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
return relayLocalArtifactPaths(paths, payload, opts, receipt.sourceRunId, receipt.toolCallId);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Adapt the shape emitted by the currently deployed OpenClaw message tool.
|
|
81
|
+
* The installed OpenClaw type declares `openclawDelivery.mediaUrls` only as a
|
|
82
|
+
* display projection. The authoritative toolResult carries the delivery
|
|
83
|
+
* status, tool call, idempotency key, run metadata, and trusted local media
|
|
84
|
+
* list in `details.sourceReply`; only that explicit relation becomes the
|
|
85
|
+
* internal typed v1 receipt. Generic mediaUrls stay inert.
|
|
86
|
+
*/
|
|
87
|
+
export function adaptOpenClawMessageToolDelivery(value, expectedSourceRunId) {
|
|
88
|
+
const envelope = asRecord(value);
|
|
89
|
+
if (!envelope)
|
|
90
|
+
return undefined;
|
|
91
|
+
const candidates = [
|
|
92
|
+
asRecord(envelope.message),
|
|
93
|
+
asRecord(envelope.toolResult),
|
|
94
|
+
...(Array.isArray(envelope.toolResults) ? envelope.toolResults.map(asRecord) : []),
|
|
95
|
+
envelope.role === "toolResult" || envelope.role === "tool_result" ? envelope : undefined,
|
|
96
|
+
].filter((candidate) => Boolean(candidate));
|
|
97
|
+
for (const message of candidates) {
|
|
98
|
+
const details = asRecord(message.details);
|
|
99
|
+
const sourceReply = asRecord(details?.sourceReply);
|
|
100
|
+
const messageDelivery = asRecord(details?.messageDelivery);
|
|
101
|
+
const metadata = asRecord(message.__openclaw);
|
|
102
|
+
const toolName = firstString(message.toolName, message.tool_name);
|
|
103
|
+
const toolCallId = firstString(message.toolCallId, message.tool_call_id);
|
|
104
|
+
const idempotencyKey = firstString(details?.idempotencyKey, message.idempotencyKey, message.idempotency_key);
|
|
105
|
+
const sourceRunId = firstString(metadata?.runId, metadata?.sourceRunId, message.runId, message.sourceRunId, envelope.runId, envelope.sourceRunId);
|
|
106
|
+
const mediaUrls = sourceReply?.mediaUrls;
|
|
107
|
+
const attachments = sourceReply?.attachments;
|
|
108
|
+
const trustedAttachments = Array.isArray(attachments)
|
|
109
|
+
&& attachments.length > 0
|
|
110
|
+
&& attachments.every((attachment) => asRecord(attachment)?.trustedLocalMedia === true);
|
|
111
|
+
if ((message.role !== "toolResult" && message.role !== "tool_result")
|
|
112
|
+
|| toolName !== "message"
|
|
113
|
+
|| !toolCallId
|
|
114
|
+
|| !idempotencyKey
|
|
115
|
+
|| !sourceRunId
|
|
116
|
+
|| (expectedSourceRunId && sourceRunId !== expectedSourceRunId)
|
|
117
|
+
|| details?.status !== "ok"
|
|
118
|
+
|| details.deliveryStatus !== "sent"
|
|
119
|
+
|| details.sourceReplyDeliveryMode !== "message_tool_only"
|
|
120
|
+
|| details.sourceReplyTranscriptOwner !== true
|
|
121
|
+
|| details.dryRun === true
|
|
122
|
+
|| sourceReply?.trustedLocalMedia !== true
|
|
123
|
+
|| !trustedAttachments
|
|
124
|
+
|| messageDelivery?.status !== "settled"
|
|
125
|
+
|| messageDelivery.partialDelivery !== false
|
|
126
|
+
|| !Array.isArray(mediaUrls)
|
|
127
|
+
|| !mediaUrls.every((path) => typeof path === "string" && path.trim().length > 0)) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
contract: OPENCLAW_DELIVERY_CONTRACT,
|
|
132
|
+
toolName: "message",
|
|
133
|
+
toolCallId,
|
|
134
|
+
idempotencyKey,
|
|
135
|
+
sourceRunId,
|
|
136
|
+
mediaUrls: mediaUrls.map((path) => path.trim()),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
function typedOpenClawDeliveryReceipt(value, sourceRunId) {
|
|
142
|
+
const delivery = asRecord(value);
|
|
143
|
+
if (!delivery
|
|
144
|
+
|| delivery.contract !== OPENCLAW_DELIVERY_CONTRACT
|
|
145
|
+
|| delivery.toolName !== "message"
|
|
146
|
+
|| typeof delivery.toolCallId !== "string"
|
|
147
|
+
|| !delivery.toolCallId.trim()
|
|
148
|
+
|| typeof delivery.idempotencyKey !== "string"
|
|
149
|
+
|| !delivery.idempotencyKey.trim()
|
|
150
|
+
|| delivery.sourceRunId !== sourceRunId
|
|
151
|
+
|| !Array.isArray(delivery.mediaUrls)
|
|
152
|
+
|| !delivery.mediaUrls.every((path) => typeof path === "string" && path.trim().length > 0)) {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
contract: OPENCLAW_DELIVERY_CONTRACT,
|
|
157
|
+
toolName: "message",
|
|
158
|
+
toolCallId: delivery.toolCallId.trim(),
|
|
159
|
+
idempotencyKey: delivery.idempotencyKey.trim(),
|
|
160
|
+
sourceRunId,
|
|
161
|
+
mediaUrls: delivery.mediaUrls.map((path) => path.trim()),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function relayOpenClawDeliveryMediaInTimelineEvents(events, blocks, sourceRunId, sessionDefaults) {
|
|
165
|
+
if (!Array.isArray(events) || blocks.length === 0 || !sourceRunId) {
|
|
166
|
+
return { events, changed: false };
|
|
167
|
+
}
|
|
168
|
+
let changed = false;
|
|
169
|
+
const nextEvents = events.map((event) => {
|
|
170
|
+
const record = asRecord(event);
|
|
171
|
+
if (!record || record.eventType !== "message.completed" || record.role !== "assistant") {
|
|
172
|
+
return event;
|
|
173
|
+
}
|
|
174
|
+
const eventRunId = firstString(record.runId);
|
|
175
|
+
const eventTurnId = firstString(record.turnId);
|
|
176
|
+
const matchesSourceRun = eventRunId === sourceRunId || (!eventRunId && eventTurnId === sourceRunId);
|
|
177
|
+
if (!matchesSourceRun) {
|
|
178
|
+
return event;
|
|
179
|
+
}
|
|
180
|
+
if (!Array.isArray(record.content)) {
|
|
181
|
+
return event;
|
|
182
|
+
}
|
|
183
|
+
const eventRelation = extractOpenClawMessageToolRelation(record);
|
|
184
|
+
const eventScope = canonicalizeOpenClawSessionScope(firstString(record.sessionKey, record.sessionId), sessionDefaults);
|
|
185
|
+
const matchingEvents = events.filter((candidate) => {
|
|
186
|
+
const candidateRecord = asRecord(candidate);
|
|
187
|
+
if (!candidateRecord || candidateRecord.eventType !== "message.completed" || candidateRecord.role !== "assistant") {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
if (eventScope) {
|
|
191
|
+
const candidateScope = canonicalizeOpenClawSessionScope(firstString(candidateRecord.sessionKey, candidateRecord.sessionId), sessionDefaults);
|
|
192
|
+
if (candidateScope !== eventScope)
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
const eventRunId = firstString(candidateRecord.runId);
|
|
196
|
+
const eventTurnId = firstString(candidateRecord.turnId);
|
|
197
|
+
return eventRunId === sourceRunId || (!eventRunId && eventTurnId === sourceRunId);
|
|
198
|
+
});
|
|
199
|
+
const relationBlocks = blocks.filter((block) => {
|
|
200
|
+
const relation = extractOpenClawMessageToolRelation(block);
|
|
201
|
+
if (relation?.sourceRunId !== sourceRunId || !relation.toolCallId)
|
|
202
|
+
return false;
|
|
203
|
+
if (!eventScope)
|
|
204
|
+
return true;
|
|
205
|
+
const blockScope = canonicalizeOpenClawSessionScope(firstString(block.sessionKey, block.sessionId), sessionDefaults);
|
|
206
|
+
return !blockScope || blockScope === eventScope;
|
|
207
|
+
});
|
|
208
|
+
let eventBlocks = blocks;
|
|
209
|
+
if (relationBlocks.length > 0) {
|
|
210
|
+
if (!eventRelation?.toolCallId) {
|
|
211
|
+
// A run-level completion can own delivery blocks only when it is the
|
|
212
|
+
// sole completion for that run. Otherwise association is ambiguous;
|
|
213
|
+
// do not duplicate the media onto every assistant row.
|
|
214
|
+
if (matchingEvents.length !== 1)
|
|
215
|
+
return event;
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
const matchingRelation = relationBlocks.filter((block) => (extractOpenClawMessageToolRelation(block)?.toolCallId === eventRelation.toolCallId));
|
|
219
|
+
if (matchingRelation.length === 0)
|
|
220
|
+
return event;
|
|
221
|
+
eventBlocks = matchingRelation;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const appended = appendUniqueContentBlocks(record.content, eventBlocks);
|
|
225
|
+
const attachmentIds = attachmentIdsFromContent(appended.blocks);
|
|
226
|
+
const currentAttachmentIds = Array.isArray(record.attachmentIds)
|
|
227
|
+
? record.attachmentIds.filter((value) => typeof value === "string" && value.trim().length > 0)
|
|
228
|
+
: [];
|
|
229
|
+
const attachmentIdsChanged = JSON.stringify(currentAttachmentIds) !== JSON.stringify(attachmentIds);
|
|
230
|
+
if (!appended.changed && !attachmentIdsChanged) {
|
|
231
|
+
return event;
|
|
232
|
+
}
|
|
233
|
+
changed = true;
|
|
234
|
+
const { attachmentIds: _staleAttachmentIds, ...eventWithoutAttachmentIds } = record;
|
|
235
|
+
return {
|
|
236
|
+
...eventWithoutAttachmentIds,
|
|
237
|
+
content: appended.blocks,
|
|
238
|
+
...(attachmentIds.length > 0 ? { attachmentIds } : {}),
|
|
239
|
+
};
|
|
240
|
+
});
|
|
241
|
+
return { events: changed ? nextEvents : events, changed };
|
|
242
|
+
}
|
|
243
|
+
function appendUniqueContentBlocks(content, additions) {
|
|
244
|
+
if (additions.length === 0)
|
|
245
|
+
return { blocks: content, changed: false };
|
|
246
|
+
const existingIdentities = new Set(content.map(contentBlockIdentity).filter((identity) => Boolean(identity)));
|
|
247
|
+
const blocks = [...content];
|
|
248
|
+
let changed = false;
|
|
249
|
+
for (const addition of additions) {
|
|
250
|
+
const identity = contentBlockIdentity(addition);
|
|
251
|
+
if (identity && existingIdentities.has(identity))
|
|
252
|
+
continue;
|
|
253
|
+
blocks.push(addition);
|
|
254
|
+
if (identity)
|
|
255
|
+
existingIdentities.add(identity);
|
|
256
|
+
changed = true;
|
|
257
|
+
}
|
|
258
|
+
return { blocks, changed };
|
|
259
|
+
}
|
|
260
|
+
function contentBlockIdentity(block) {
|
|
261
|
+
const record = asRecord(block);
|
|
262
|
+
if (!record)
|
|
263
|
+
return undefined;
|
|
264
|
+
const sourceRunId = firstString(record.sourceRunId, record.source_run_id);
|
|
265
|
+
const toolCallId = firstString(record.toolCallId, record.tool_call_id, record.sourceToolCallId, record.source_tool_call_id);
|
|
266
|
+
const mediaId = firstString(record.attachmentId, record.attachment_id, record.fileId, record.file_id, record.artifactId, record.artifact_id, record.url, record.openUrl, record.downloadUrl, record.downloadPath, record.download_path);
|
|
267
|
+
if (sourceRunId && toolCallId) {
|
|
268
|
+
return `openclaw-tool-call\u0000${sourceRunId}\u0000${toolCallId}\u0000${mediaId ?? "media"}`;
|
|
269
|
+
}
|
|
270
|
+
return mediaId;
|
|
271
|
+
}
|
|
41
272
|
async function relayOutgoingMediaInTimelineEvents(events, opts, fallbackSourceRunId) {
|
|
42
273
|
if (!Array.isArray(events))
|
|
43
274
|
return { events, changed: false };
|
|
@@ -46,8 +277,16 @@ async function relayOutgoingMediaInTimelineEvents(events, opts, fallbackSourceRu
|
|
|
46
277
|
const record = asRecord(event);
|
|
47
278
|
if (!record || !Array.isArray(record.content))
|
|
48
279
|
return event;
|
|
49
|
-
const
|
|
50
|
-
const
|
|
280
|
+
const eventRelation = extractOpenClawMessageToolRelation(record);
|
|
281
|
+
const sourceRunId = eventRelation?.sourceRunId
|
|
282
|
+
?? firstString(record.runId, record.turnId, fallbackSourceRunId);
|
|
283
|
+
const sessionKey = canonicalRelationSessionKey(firstString(record.sessionKey, record.sessionId), opts);
|
|
284
|
+
const content = await relayOutgoingMediaContent(record.content, {
|
|
285
|
+
...opts,
|
|
286
|
+
sourceRunId,
|
|
287
|
+
...(sessionKey ? { sessionKey } : {}),
|
|
288
|
+
...(eventRelation?.toolCallId ? { toolCallId: eventRelation.toolCallId } : {}),
|
|
289
|
+
});
|
|
51
290
|
if (!content.changed)
|
|
52
291
|
return event;
|
|
53
292
|
changed = true;
|
|
@@ -87,49 +326,101 @@ export async function relayOutgoingMediaInHistoryResponse(response, opts) {
|
|
|
87
326
|
return response;
|
|
88
327
|
}
|
|
89
328
|
const responseRecord = response;
|
|
90
|
-
const responseSessionKey = firstString(responseRecord.sessionKey, responseRecord.sessionId);
|
|
91
|
-
const explicitSidecars = normalizeOpenClawAssistantMediaSidecars(messages, responseSessionKey);
|
|
92
|
-
const normalizedMessages = normalizeOpenClawAutomaticMediaReplies(explicitSidecars.messages, responseSessionKey);
|
|
93
|
-
const messageResult = await relayOutgoingMediaInMessageList(normalizedMessages.messages, responseSessionKey, opts);
|
|
329
|
+
const responseSessionKey = canonicalRelationSessionKey(firstString(responseRecord.sessionKey, responseRecord.sessionId), opts);
|
|
94
330
|
const snapshot = responseRecord.timelineSnapshot;
|
|
95
331
|
const snapshotMessages = snapshot && typeof snapshot === "object" && !Array.isArray(snapshot)
|
|
96
332
|
? snapshot.messages
|
|
97
333
|
: undefined;
|
|
334
|
+
const projectionV3 = Array.isArray(snapshotMessages)
|
|
335
|
+
&& snapshotMessages.some((message) => (Boolean(message) && typeof message === "object" && message.projectionVersion === 3));
|
|
336
|
+
const projectionOptions = projectionV3 ? { projectionVersion: 3 } : undefined;
|
|
337
|
+
const relationOptions = {
|
|
338
|
+
...(projectionOptions ?? {}),
|
|
339
|
+
...(opts.sessionDefaults ? { sessionDefaults: opts.sessionDefaults } : {}),
|
|
340
|
+
};
|
|
341
|
+
const explicitSidecars = normalizeOpenClawAssistantMediaSidecars(messages, responseSessionKey, relationOptions);
|
|
342
|
+
const normalizedMessages = normalizeOpenClawAutomaticMediaReplies(explicitSidecars.messages, responseSessionKey, relationOptions);
|
|
343
|
+
const messageResult = await relayOutgoingMediaInMessageList(normalizedMessages.messages, responseSessionKey, opts);
|
|
98
344
|
const explicitSnapshotSidecars = Array.isArray(snapshotMessages)
|
|
99
|
-
? normalizeOpenClawAssistantMediaSidecars(snapshotMessages, responseSessionKey)
|
|
345
|
+
? normalizeOpenClawAssistantMediaSidecars(snapshotMessages, responseSessionKey, relationOptions)
|
|
100
346
|
: undefined;
|
|
101
347
|
const normalizedSnapshotMessages = explicitSnapshotSidecars
|
|
102
|
-
? normalizeOpenClawAutomaticMediaReplies(explicitSnapshotSidecars.messages, responseSessionKey)
|
|
348
|
+
? normalizeOpenClawAutomaticMediaReplies(explicitSnapshotSidecars.messages, responseSessionKey, relationOptions)
|
|
103
349
|
: undefined;
|
|
104
350
|
const snapshotResult = normalizedSnapshotMessages
|
|
105
351
|
? await relayOutgoingMediaInMessageList(normalizedSnapshotMessages.messages, responseSessionKey, opts)
|
|
106
352
|
: undefined;
|
|
353
|
+
// The history projector builds the canonical snapshot before this relay
|
|
354
|
+
// enriches the provider rows. Reuse the already-relayed row by its explicit
|
|
355
|
+
// source identity so the canonical timeline receives the same attachment
|
|
356
|
+
// blocks without re-reading local paths or merging by run/text/time.
|
|
357
|
+
const reconciledSnapshot = snapshotResult
|
|
358
|
+
? reconcileHistorySnapshotWithRelayedMessages(snapshotResult.messages, messageResult.messages)
|
|
359
|
+
: undefined;
|
|
107
360
|
const changed = explicitSidecars.changed
|
|
108
361
|
|| normalizedMessages.changed
|
|
109
362
|
|| messageResult.changed
|
|
110
363
|
|| Boolean(explicitSnapshotSidecars?.changed)
|
|
111
364
|
|| Boolean(normalizedSnapshotMessages?.changed)
|
|
112
|
-
|| Boolean(snapshotResult?.changed)
|
|
365
|
+
|| Boolean(snapshotResult?.changed)
|
|
366
|
+
|| Boolean(reconciledSnapshot?.changed);
|
|
113
367
|
return changed
|
|
114
368
|
? {
|
|
115
369
|
...responseRecord,
|
|
116
370
|
messages: messageResult.messages,
|
|
117
|
-
...(
|
|
371
|
+
...(reconciledSnapshot
|
|
118
372
|
? {
|
|
119
373
|
timelineSnapshot: {
|
|
120
374
|
...snapshot,
|
|
121
|
-
messages:
|
|
375
|
+
messages: reconciledSnapshot.messages,
|
|
122
376
|
},
|
|
123
377
|
}
|
|
124
378
|
: {}),
|
|
125
379
|
}
|
|
126
380
|
: response;
|
|
127
381
|
}
|
|
382
|
+
function reconcileHistorySnapshotWithRelayedMessages(snapshotMessages, relayedMessages) {
|
|
383
|
+
const bySourceIdentity = new Map();
|
|
384
|
+
for (const message of relayedMessages) {
|
|
385
|
+
const record = asRecord(message);
|
|
386
|
+
const identity = historySourceIdentity(record);
|
|
387
|
+
if (record && identity)
|
|
388
|
+
bySourceIdentity.set(identity, record);
|
|
389
|
+
}
|
|
390
|
+
let changed = false;
|
|
391
|
+
const messages = snapshotMessages.map((message) => {
|
|
392
|
+
const record = asRecord(message);
|
|
393
|
+
const source = record ? bySourceIdentity.get(historySourceIdentity(record) ?? "") : undefined;
|
|
394
|
+
if (!record || !source || !Array.isArray(source.content) || source.content.length === 0) {
|
|
395
|
+
return message;
|
|
396
|
+
}
|
|
397
|
+
const content = source.content;
|
|
398
|
+
const attachmentIds = attachmentIdsFromContent(content);
|
|
399
|
+
const currentContent = Array.isArray(record.content) ? record.content : undefined;
|
|
400
|
+
const currentAttachmentIds = Array.isArray(record.attachmentIds) ? record.attachmentIds : undefined;
|
|
401
|
+
if (JSON.stringify(currentContent) === JSON.stringify(content)
|
|
402
|
+
&& JSON.stringify(currentAttachmentIds ?? []) === JSON.stringify(attachmentIds)) {
|
|
403
|
+
return message;
|
|
404
|
+
}
|
|
405
|
+
changed = true;
|
|
406
|
+
return {
|
|
407
|
+
...record,
|
|
408
|
+
content,
|
|
409
|
+
...(attachmentIds.length > 0 ? { attachmentIds } : {}),
|
|
410
|
+
};
|
|
411
|
+
});
|
|
412
|
+
return { messages: changed ? messages : snapshotMessages, changed };
|
|
413
|
+
}
|
|
414
|
+
function historySourceIdentity(record) {
|
|
415
|
+
return firstString(record?.sourceMessageId, record?.id, record?.messageId, record?.message_id);
|
|
416
|
+
}
|
|
128
417
|
async function relayOutgoingMediaInMessageList(messages, sessionKey, opts) {
|
|
129
418
|
let changed = false;
|
|
130
419
|
const nextMessages = await Promise.all(messages.map(async (message) => {
|
|
131
420
|
const restoredMessage = restoreOpenClawInputMediaInHistoryMessage(message);
|
|
132
|
-
const wrapperInput = sessionKey
|
|
421
|
+
const wrapperInput = sessionKey
|
|
422
|
+
? { sessionKey: canonicalRelationSessionKey(sessionKey, opts), message: restoredMessage }
|
|
423
|
+
: { message: restoredMessage };
|
|
133
424
|
const wrapper = await relayOutgoingMediaInPayload(wrapperInput, opts);
|
|
134
425
|
const nextMessage = wrapper.message ?? message;
|
|
135
426
|
changed ||= nextMessage !== message;
|
|
@@ -180,6 +471,40 @@ async function relayOutgoingMediaBlock(block, opts) {
|
|
|
180
471
|
}
|
|
181
472
|
const source = block;
|
|
182
473
|
const url = firstString(source.url, source.openUrl, source.downloadUrl, source.download_path, source.downloadPath);
|
|
474
|
+
if (url?.startsWith("media://inbound/") && opts.sessionKey && opts.sourceRunId) {
|
|
475
|
+
const attachmentId = `openclaw-input-${createHash("sha256").update(JSON.stringify([opts.sessionKey, opts.sourceRunId, url])).digest("hex").slice(0, 32)}`;
|
|
476
|
+
try {
|
|
477
|
+
const root = await realpath(join(opts.stateDir ?? resolveOpenClawStateDir(), "media", "inbound"));
|
|
478
|
+
const filePath = await realpath(resolve(root, decodeURIComponent(url.slice("media://inbound/".length))));
|
|
479
|
+
const pathWithinRoot = relative(root, filePath);
|
|
480
|
+
if (!pathWithinRoot || pathWithinRoot.startsWith("..") || isAbsolute(pathWithinRoot))
|
|
481
|
+
throw new Error("invalid_managed_media_path");
|
|
482
|
+
const cacheKey = await outgoingFileCacheKey({ gatewayId: opts.gatewayId, sessionKey: opts.sessionKey, identity: attachmentId, filePath, sourceRunId: opts.sourceRunId });
|
|
483
|
+
const upload = await cachedUpload(opts, cacheKey, {
|
|
484
|
+
relayServerUrl: opts.relayServerUrl, relaySecret: opts.relaySecret,
|
|
485
|
+
gatewayId: opts.gatewayId, sessionKey: opts.sessionKey, filePath,
|
|
486
|
+
sourceRunId: opts.sourceRunId, timelineDelivery: "embedded",
|
|
487
|
+
sourceRole: "user",
|
|
488
|
+
});
|
|
489
|
+
return {
|
|
490
|
+
...uploadToContentBlock(upload),
|
|
491
|
+
attachmentId,
|
|
492
|
+
fileName: source.fileName || upload.fileName,
|
|
493
|
+
...(opts.toolCallId ? { toolCallId: opts.toolCallId } : {}),
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
catch {
|
|
497
|
+
return {
|
|
498
|
+
type: source.type,
|
|
499
|
+
attachmentId,
|
|
500
|
+
fileName: source.fileName || "图片",
|
|
501
|
+
...(opts.toolCallId ? { toolCallId: opts.toolCallId } : {}),
|
|
502
|
+
transferState: "expired",
|
|
503
|
+
isRemoteExpired: true,
|
|
504
|
+
attachmentStatusText: "图片文件暂不可用",
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
}
|
|
183
508
|
const attachmentId = outgoingAttachmentId(url);
|
|
184
509
|
if (!attachmentId) {
|
|
185
510
|
return block;
|
|
@@ -190,11 +515,12 @@ async function relayOutgoingMediaBlock(block, opts) {
|
|
|
190
515
|
try {
|
|
191
516
|
const record = await readOutgoingMediaRecordWhenReady(attachmentId, opts);
|
|
192
517
|
const filePath = record.original?.path?.trim();
|
|
193
|
-
const
|
|
194
|
-
if (!filePath || !
|
|
518
|
+
const rawSessionKey = record.sessionKey?.trim();
|
|
519
|
+
if (!filePath || !rawSessionKey) {
|
|
195
520
|
console.warn(`[relay] outgoing media record is incomplete attachment=${attachmentId}`);
|
|
196
521
|
return undefined;
|
|
197
522
|
}
|
|
523
|
+
const sessionKey = canonicalRelationSessionKey(rawSessionKey, opts) ?? rawSessionKey;
|
|
198
524
|
const cacheKey = await outgoingFileCacheKey({
|
|
199
525
|
gatewayId: opts.gatewayId,
|
|
200
526
|
sessionKey,
|
|
@@ -211,6 +537,7 @@ async function relayOutgoingMediaBlock(block, opts) {
|
|
|
211
537
|
senderDisplayName: opts.senderDisplayName,
|
|
212
538
|
sourceRunId: opts.sourceRunId,
|
|
213
539
|
timelineDelivery: "embedded",
|
|
540
|
+
sourceRole: "assistant",
|
|
214
541
|
});
|
|
215
542
|
return {
|
|
216
543
|
...source,
|
|
@@ -229,6 +556,8 @@ async function relayOutgoingMediaBlock(block, opts) {
|
|
|
229
556
|
downloadPath: upload.downloadPath,
|
|
230
557
|
expiresAt: upload.expiresAt,
|
|
231
558
|
sourceRunId: upload.sourceRunId,
|
|
559
|
+
...(opts.toolCallId ? { toolCallId: opts.toolCallId } : {}),
|
|
560
|
+
sourceRole: upload.sourceRole ?? "assistant",
|
|
232
561
|
gatewayId: upload.gatewayId,
|
|
233
562
|
sessionKey: upload.sessionKey,
|
|
234
563
|
transferState: "available",
|
|
@@ -245,6 +574,7 @@ async function relayOutgoingMediaBlock(block, opts) {
|
|
|
245
574
|
type: typeof source.type === "string" && source.type.trim() ? source.type : "image",
|
|
246
575
|
attachmentId,
|
|
247
576
|
fileName: firstString(source.fileName, source.alt, "图片"),
|
|
577
|
+
...(opts.toolCallId ? { toolCallId: opts.toolCallId } : {}),
|
|
248
578
|
transferState: "expired",
|
|
249
579
|
isRemoteExpired: true,
|
|
250
580
|
attachmentStatusText: "图片文件在桌面端已不可用",
|
|
@@ -264,7 +594,9 @@ function payloadSourceRunId(payload) {
|
|
|
264
594
|
const messageRecord = message && typeof message === "object" && !Array.isArray(message)
|
|
265
595
|
? message
|
|
266
596
|
: undefined;
|
|
267
|
-
|
|
597
|
+
const metadata = asRecord(messageRecord?.__openclaw);
|
|
598
|
+
const inputId = firstString(messageRecord?.idempotencyKey, metadata?.idempotencyKey);
|
|
599
|
+
return firstString(payload.sourceRunId, payload.source_run_id, payload.runId, payload.turnId, payload.messageId, payload.id, messageRecord?.sourceRunId, messageRecord?.source_run_id, messageRecord?.runId, messageRecord?.turnId, metadata?.runId, inputId?.replace(/:user$/, ""), messageRecord?.messageId, messageRecord?.id, metadata?.id);
|
|
268
600
|
}
|
|
269
601
|
async function readOutgoingMediaRecord(attachmentId, options) {
|
|
270
602
|
const stateDir = options.stateDir ?? resolveOpenClawStateDir();
|
|
@@ -287,7 +619,9 @@ async function readOutgoingMediaRecordWhenReady(attachmentId, options) {
|
|
|
287
619
|
// enrichment avoids making ordinary history reads slower for genuinely
|
|
288
620
|
// expired media while giving the transaction up to roughly one second to
|
|
289
621
|
// become observable.
|
|
290
|
-
const delaysMs = options.waitForOutgoingMediaRecord
|
|
622
|
+
const delaysMs = options.waitForOutgoingMediaRecord
|
|
623
|
+
? OUTGOING_MEDIA_RECORD_WAIT_DELAYS_MS
|
|
624
|
+
: [];
|
|
291
625
|
let lastError;
|
|
292
626
|
for (const delayMs of [...delaysMs, 0]) {
|
|
293
627
|
if (delayMs > 0) {
|
|
@@ -383,29 +717,11 @@ function asRecord(value) {
|
|
|
383
717
|
? value
|
|
384
718
|
: undefined;
|
|
385
719
|
}
|
|
386
|
-
async function
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
return [];
|
|
392
|
-
}
|
|
393
|
-
const text = content
|
|
394
|
-
.map((block) => {
|
|
395
|
-
if (!block || typeof block !== "object" || Array.isArray(block)) {
|
|
396
|
-
return "";
|
|
397
|
-
}
|
|
398
|
-
const record = block;
|
|
399
|
-
return record.type === "text" && typeof record.text === "string" ? record.text : "";
|
|
400
|
-
})
|
|
401
|
-
.filter(Boolean)
|
|
402
|
-
.join("\n");
|
|
403
|
-
const paths = extractDeliverablePaths(text, opts.userMessage);
|
|
404
|
-
if (paths.length === 0) {
|
|
405
|
-
return [];
|
|
406
|
-
}
|
|
407
|
-
const sessionKey = firstString(payload.sessionKey, payload.message?.sessionKey) ?? "main";
|
|
408
|
-
const runId = payloadSourceRunId(payload);
|
|
720
|
+
async function relayLocalArtifactPaths(paths, payload, opts, sourceRunIdOverride, toolCallId) {
|
|
721
|
+
const messageRecord = asRecord(payload.message);
|
|
722
|
+
const rawSessionKey = firstString(messageRecord?.sessionKey, messageRecord?.sessionId, payload.sessionKey) ?? "main";
|
|
723
|
+
const sessionKey = canonicalRelationSessionKey(rawSessionKey, opts) ?? rawSessionKey;
|
|
724
|
+
const runId = sourceRunIdOverride ?? payloadSourceRunId(payload);
|
|
409
725
|
const blocks = [];
|
|
410
726
|
for (const filePath of paths) {
|
|
411
727
|
try {
|
|
@@ -425,9 +741,10 @@ async function relayLocalArtifactPathsInContent(content, payload, opts) {
|
|
|
425
741
|
senderDisplayName: opts.senderDisplayName,
|
|
426
742
|
sourceRunId: runId,
|
|
427
743
|
timelineDelivery: "embedded",
|
|
744
|
+
sourceRole: "assistant",
|
|
428
745
|
};
|
|
429
746
|
const upload = await cachedUpload(opts, cacheKey, request);
|
|
430
|
-
blocks.push(uploadToContentBlock(upload));
|
|
747
|
+
blocks.push(uploadToContentBlock(upload, toolCallId));
|
|
431
748
|
}
|
|
432
749
|
catch (error) {
|
|
433
750
|
console.warn(`[relay] failed to publish local artifact ${filePath}: ${String(error)}`);
|
|
@@ -459,7 +776,7 @@ function attachmentIdsFromContent(content) {
|
|
|
459
776
|
}
|
|
460
777
|
return [...ids];
|
|
461
778
|
}
|
|
462
|
-
function uploadToContentBlock(upload) {
|
|
779
|
+
function uploadToContentBlock(upload, toolCallId) {
|
|
463
780
|
const type = upload.mimeType.startsWith("image/")
|
|
464
781
|
? "image"
|
|
465
782
|
: upload.mimeType.startsWith("audio/")
|
|
@@ -483,6 +800,8 @@ function uploadToContentBlock(upload) {
|
|
|
483
800
|
downloadPath: upload.downloadPath,
|
|
484
801
|
expiresAt: upload.expiresAt,
|
|
485
802
|
sourceRunId: upload.sourceRunId,
|
|
803
|
+
...(toolCallId ? { toolCallId } : {}),
|
|
804
|
+
sourceRole: upload.sourceRole,
|
|
486
805
|
sha256: upload.sha256,
|
|
487
806
|
contentHash: upload.sha256,
|
|
488
807
|
gatewayId: upload.gatewayId,
|
|
@@ -543,6 +862,16 @@ async function cachedUpload(opts, cacheKey, request) {
|
|
|
543
862
|
inFlight.delete(cacheKey);
|
|
544
863
|
}
|
|
545
864
|
}
|
|
865
|
+
function canonicalRelationSessionKey(rawSessionKey, opts) {
|
|
866
|
+
if (!rawSessionKey)
|
|
867
|
+
return undefined;
|
|
868
|
+
const gatewayKey = opts.sessionDefaults
|
|
869
|
+
? canonicalizeSessionKey(rawSessionKey, opts.sessionDefaults)
|
|
870
|
+
: rawSessionKey;
|
|
871
|
+
if (typeof gatewayKey !== "string" || !gatewayKey.trim())
|
|
872
|
+
return undefined;
|
|
873
|
+
return canonicalizeOpenClawSessionScope(gatewayKey, opts.sessionDefaults);
|
|
874
|
+
}
|
|
546
875
|
function sanitizeOpenClawMediaControlBlocks(content) {
|
|
547
876
|
let changed = false;
|
|
548
877
|
const nextContent = [];
|
|
@@ -603,71 +932,31 @@ const DELIVERABLE_EXTENSIONS = [
|
|
|
603
932
|
".pptx", ".ppt", ".odp", ".zip", ".tar", ".gz", ".tgz", ".bz2", ".7z",
|
|
604
933
|
".html", ".htm",
|
|
605
934
|
];
|
|
606
|
-
|
|
607
|
-
.map((extension) => extension.slice(1).replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
|
|
608
|
-
.sort((a, b) => b.length - a.length)
|
|
609
|
-
.join("|");
|
|
610
|
-
const DELIVERABLE_PATH_START = String.raw `(?:~[\\/]|\/|[A-Za-z]:[\\/]|\\\\[^\\/\s"'` + "`" + String.raw `<>|]+[\\/])`;
|
|
611
|
-
const DELIVERABLE_PATH_REGEX_SOURCE = DELIVERABLE_PATH_START + String.raw `[^\n"'` + "`" + String.raw `<>|]*?\.(?:${DELIVERABLE_EXTENSION_PATTERN})(?=$|[\s).,"'` + "`" + String.raw `,。;;::!??])`;
|
|
612
|
-
const DELIVERABLE_PATH_REGEX = new RegExp(String.raw `(?:^|[\s("'` + "`" + String.raw `::])(${DELIVERABLE_PATH_REGEX_SOURCE})`, "gi");
|
|
613
|
-
export function extractDeliverablePathCandidates(text) {
|
|
614
|
-
return [...text.matchAll(DELIVERABLE_PATH_REGEX)].map((match) => match[1]);
|
|
615
|
-
}
|
|
616
|
-
function extractDeliverablePaths(text, userMessage) {
|
|
617
|
-
if (userMessage !== undefined && !hasDeliverableSendIntent(userMessage)) {
|
|
618
|
-
return [];
|
|
619
|
-
}
|
|
620
|
-
const allowed = new Set(DELIVERABLE_EXTENSIONS);
|
|
935
|
+
function trustedDeliverablePaths(values) {
|
|
621
936
|
const paths = new Set();
|
|
622
|
-
for (const
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
if (
|
|
627
|
-
paths.add(
|
|
628
|
-
}
|
|
937
|
+
for (const value of values) {
|
|
938
|
+
if (typeof value !== "string")
|
|
939
|
+
continue;
|
|
940
|
+
const filePath = trustedDeliverablePath(value);
|
|
941
|
+
if (filePath)
|
|
942
|
+
paths.add(filePath);
|
|
629
943
|
}
|
|
630
944
|
return [...paths];
|
|
631
945
|
}
|
|
632
|
-
function
|
|
633
|
-
const
|
|
634
|
-
if (!
|
|
635
|
-
return
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|| /(只要|仅要).{0,20}(路径|文件名|名字|文本|文字)/.test(text)
|
|
639
|
-
|| /\b(do not|don't|dont|no need to|without|not need to)\b.{0,30}\b(send|upload|attach|share|deliver)\b/.test(text)
|
|
640
|
-
|| /\bonly\b.{0,30}\b(path|filename|file name|text)\b/.test(text)) {
|
|
641
|
-
return false;
|
|
946
|
+
function trustedDeliverablePath(rawPath) {
|
|
947
|
+
const candidate = rawPath.trim();
|
|
948
|
+
if (!candidate)
|
|
949
|
+
return undefined;
|
|
950
|
+
if (!candidate.startsWith("~/") && !candidate.startsWith("~\\") && !isAbsoluteHostPath(candidate)) {
|
|
951
|
+
return undefined;
|
|
642
952
|
}
|
|
643
|
-
const
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
"file", "image", "photo", "picture", "attachment", "document", "report", "spreadsheet", "screenshot", "archive",
|
|
649
|
-
"zip", "pdf", "doc", "docx", "xlsx", "ppt", "pptx",
|
|
650
|
-
].join("|");
|
|
651
|
-
const pathIntentPattern = new RegExp(`(?:(${sendVerbs}).{0,80}${DELIVERABLE_PATH_REGEX_SOURCE}|${DELIVERABLE_PATH_REGEX_SOURCE}.{0,80}(${sendVerbs}))`);
|
|
652
|
-
if (pathIntentPattern.test(text)) {
|
|
653
|
-
return true;
|
|
654
|
-
}
|
|
655
|
-
if (new RegExp(`(你.{0,6}(能|可以|会)|能不能|可不可以|能否|是否|会不会|支持).{0,30}(${sendVerbs}).{0,30}(${deliverableWords}).{0,8}(吗|么|嘛|\\?|?)?`).test(text)) {
|
|
656
|
-
return false;
|
|
953
|
+
const expandedPath = candidate.startsWith("~/") || candidate.startsWith("~\\")
|
|
954
|
+
? join(homedir(), candidate.slice(2))
|
|
955
|
+
: candidate;
|
|
956
|
+
if (!DELIVERABLE_EXTENSIONS.includes(extname(expandedPath).toLowerCase())) {
|
|
957
|
+
return undefined;
|
|
657
958
|
}
|
|
658
|
-
const
|
|
659
|
-
|
|
660
|
-
new RegExp(`(${sendVerbs})(这张|这个|这份|该|那张|那份|一张|几张|一些|些|一下)?[^,。,.!??]{0,24}(${deliverableWords})(给我|到手机|到移动端|过来|回来|一下|给这边)?`),
|
|
661
|
-
new RegExp(`(${sendVerbs})(给我|到手机|到移动端|过来|回来).{0,50}(${deliverableWords})?`),
|
|
662
|
-
new RegExp(`(给我|帮我).{0,20}(${sendVerbs}).{0,50}(${deliverableWords})`),
|
|
663
|
-
];
|
|
664
|
-
if (directChinesePatterns.some((pattern) => pattern.test(text))) {
|
|
665
|
-
return true;
|
|
666
|
-
}
|
|
667
|
-
const englishPatterns = [
|
|
668
|
-
new RegExp(`\\b(send|upload|attach|share|deliver)\\b.{0,50}\\b(${deliverableWords})\\b`),
|
|
669
|
-
new RegExp(`\\b(${deliverableWords})\\b.{0,50}\\b(send|upload|attach|share|deliver)\\b`),
|
|
670
|
-
];
|
|
671
|
-
return englishPatterns.some((pattern) => pattern.test(text));
|
|
959
|
+
const filePath = resolve(expandedPath);
|
|
960
|
+
return existsSync(filePath) ? filePath : undefined;
|
|
672
961
|
}
|
|
673
962
|
//# sourceMappingURL=outgoing-media-relay.js.map
|