@alfe.ai/openclaw-chat 0.8.1 → 0.8.2

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/plugin2.cjs CHANGED
@@ -106,9 +106,9 @@ function extensionOf(p) {
106
106
  function isRemoteUrl(ref) {
107
107
  return /^https?:\/\//i.test(ref);
108
108
  }
109
- /** Non-http(s) URI schemes (data:, blob:, media://inbound claim-checks…) —
110
- * not local paths, nothing we can upload. Windows drive letters (C:\) are
111
- * paths, not schemes. */
109
+ /** Non-http(s) URI schemes (data:, blob:, …) — not local paths, nothing we
110
+ * can upload. Windows drive letters (C:\) are paths, not schemes.
111
+ * `media://inbound/<id>` is handled separately (resolveOpenClawMediaPath). */
112
112
  function hasNonPathScheme(ref) {
113
113
  if (/^file:\/\//i.test(ref)) return false;
114
114
  if (/^[a-zA-Z]:[\\/]/.test(ref)) return false;
@@ -120,6 +120,34 @@ function resolveLocalMediaPath(ref) {
120
120
  if (p === "~" || p.startsWith("~/")) p = (0, node_path.join)((0, node_os.homedir)(), p.slice(1));
121
121
  return (0, node_path.isAbsolute)(p) ? p : (0, node_path.resolve)(process.cwd(), p);
122
122
  }
123
+ const MEDIA_INBOUND_RE = /^media:\/\/inbound\/([^/\\]+)$/i;
124
+ /** Resolve a `media://inbound/<id>` URI to its media-store file, or null if
125
+ * the ref isn't one (or the id is unsafe). Traversal-hardened: the id must
126
+ * be a single path segment. */
127
+ function resolveOpenClawMediaPath(ref) {
128
+ const match = MEDIA_INBOUND_RE.exec(ref.trim());
129
+ if (!match) return null;
130
+ let id = match[1];
131
+ try {
132
+ id = decodeURIComponent(id);
133
+ } catch {}
134
+ if (!id || id === "." || id === ".." || /[/\\\0]/.test(id)) return null;
135
+ const stateDirOverride = process.env.OPENCLAW_STATE_DIR?.trim();
136
+ const home = process.env.OPENCLAW_HOME?.trim();
137
+ return (0, node_path.join)(stateDirOverride?.length ? stateDirOverride : (0, node_path.join)(home?.length ? home : (0, node_os.homedir)(), ".openclaw"), "media", "inbound", id);
138
+ }
139
+ /**
140
+ * Map an outbound ref to the local file we would upload: media-store path
141
+ * for `media://inbound/…`, filesystem path for path-shaped refs, null for
142
+ * remote URLs and unsupported schemes (data:, blob:, …).
143
+ */
144
+ function toLocalUploadPath(ref) {
145
+ if (isRemoteUrl(ref)) return null;
146
+ const mediaStorePath = resolveOpenClawMediaPath(ref);
147
+ if (mediaStorePath) return mediaStorePath;
148
+ if (hasNonPathScheme(ref)) return null;
149
+ return resolveLocalMediaPath(ref);
150
+ }
123
151
  async function uploadLocalFile(localPath, log, deps) {
124
152
  const client = deps.getClient ? deps.getClient() : getUploadClient(log);
125
153
  if (!client) return null;
@@ -197,11 +225,11 @@ function resolveOutboundMediaRef(ref, ctx, log, deps = {}) {
197
225
  const trimmed = ref.trim();
198
226
  if (!trimmed) return Promise.resolve(null);
199
227
  if (isRemoteUrl(trimmed)) return Promise.resolve({ url: trimmed });
200
- if (hasNonPathScheme(trimmed)) {
228
+ const localPath = toLocalUploadPath(trimmed);
229
+ if (!localPath) {
201
230
  log.warn(`Outbound media ref has unsupported scheme — skipping: ${trimmed.slice(0, 120)}`);
202
231
  return Promise.resolve(null);
203
232
  }
204
- const localPath = resolveLocalMediaPath(trimmed);
205
233
  const cached = ctx.uploads.get(localPath);
206
234
  if (cached) return cached;
207
235
  const promise = ctx.uploads.size >= 10 ? (() => {
@@ -230,8 +258,8 @@ async function resolveOutboundMedia(refs, ctx, log, deps = {}) {
230
258
  attachments.push(resolved);
231
259
  continue;
232
260
  }
233
- if (isRemoteUrl(trimmed) || hasNonPathScheme(trimmed)) continue;
234
- const localPath = resolveLocalMediaPath(trimmed);
261
+ const localPath = toLocalUploadPath(trimmed);
262
+ if (!localPath) continue;
235
263
  if (!ctx.annotatedFailures.has(localPath)) {
236
264
  ctx.annotatedFailures.add(localPath);
237
265
  newFailures.push((0, node_path.basename)(localPath));
@@ -327,11 +355,16 @@ async function rewriteLocalMarkdownImages(text, ctx, log, deps = {}) {
327
355
  const start = match.index;
328
356
  out += seg.text.slice(cursor, start);
329
357
  cursor = start + full.length;
330
- if (isRemoteUrl(src) || hasNonPathScheme(src)) {
358
+ if (isRemoteUrl(src)) {
359
+ out += full;
360
+ continue;
361
+ }
362
+ const localPath = toLocalUploadPath(src);
363
+ if (!localPath) {
331
364
  out += full;
332
365
  continue;
333
366
  }
334
- const ext = extensionOf(src.split("?")[0]);
367
+ const ext = extensionOf(localPath.split("?")[0]);
335
368
  if (!ext || !IMAGE_EXTENSIONS.has(ext)) {
336
369
  out += full;
337
370
  continue;
package/dist/plugin2.js CHANGED
@@ -106,9 +106,9 @@ function extensionOf(p) {
106
106
  function isRemoteUrl(ref) {
107
107
  return /^https?:\/\//i.test(ref);
108
108
  }
109
- /** Non-http(s) URI schemes (data:, blob:, media://inbound claim-checks…) —
110
- * not local paths, nothing we can upload. Windows drive letters (C:\) are
111
- * paths, not schemes. */
109
+ /** Non-http(s) URI schemes (data:, blob:, …) — not local paths, nothing we
110
+ * can upload. Windows drive letters (C:\) are paths, not schemes.
111
+ * `media://inbound/<id>` is handled separately (resolveOpenClawMediaPath). */
112
112
  function hasNonPathScheme(ref) {
113
113
  if (/^file:\/\//i.test(ref)) return false;
114
114
  if (/^[a-zA-Z]:[\\/]/.test(ref)) return false;
@@ -120,6 +120,34 @@ function resolveLocalMediaPath(ref) {
120
120
  if (p === "~" || p.startsWith("~/")) p = join(homedir(), p.slice(1));
121
121
  return isAbsolute(p) ? p : resolve(process.cwd(), p);
122
122
  }
123
+ const MEDIA_INBOUND_RE = /^media:\/\/inbound\/([^/\\]+)$/i;
124
+ /** Resolve a `media://inbound/<id>` URI to its media-store file, or null if
125
+ * the ref isn't one (or the id is unsafe). Traversal-hardened: the id must
126
+ * be a single path segment. */
127
+ function resolveOpenClawMediaPath(ref) {
128
+ const match = MEDIA_INBOUND_RE.exec(ref.trim());
129
+ if (!match) return null;
130
+ let id = match[1];
131
+ try {
132
+ id = decodeURIComponent(id);
133
+ } catch {}
134
+ if (!id || id === "." || id === ".." || /[/\\\0]/.test(id)) return null;
135
+ const stateDirOverride = process.env.OPENCLAW_STATE_DIR?.trim();
136
+ const home = process.env.OPENCLAW_HOME?.trim();
137
+ return join(stateDirOverride?.length ? stateDirOverride : join(home?.length ? home : homedir(), ".openclaw"), "media", "inbound", id);
138
+ }
139
+ /**
140
+ * Map an outbound ref to the local file we would upload: media-store path
141
+ * for `media://inbound/…`, filesystem path for path-shaped refs, null for
142
+ * remote URLs and unsupported schemes (data:, blob:, …).
143
+ */
144
+ function toLocalUploadPath(ref) {
145
+ if (isRemoteUrl(ref)) return null;
146
+ const mediaStorePath = resolveOpenClawMediaPath(ref);
147
+ if (mediaStorePath) return mediaStorePath;
148
+ if (hasNonPathScheme(ref)) return null;
149
+ return resolveLocalMediaPath(ref);
150
+ }
123
151
  async function uploadLocalFile(localPath, log, deps) {
124
152
  const client = deps.getClient ? deps.getClient() : getUploadClient(log);
125
153
  if (!client) return null;
@@ -197,11 +225,11 @@ function resolveOutboundMediaRef(ref, ctx, log, deps = {}) {
197
225
  const trimmed = ref.trim();
198
226
  if (!trimmed) return Promise.resolve(null);
199
227
  if (isRemoteUrl(trimmed)) return Promise.resolve({ url: trimmed });
200
- if (hasNonPathScheme(trimmed)) {
228
+ const localPath = toLocalUploadPath(trimmed);
229
+ if (!localPath) {
201
230
  log.warn(`Outbound media ref has unsupported scheme — skipping: ${trimmed.slice(0, 120)}`);
202
231
  return Promise.resolve(null);
203
232
  }
204
- const localPath = resolveLocalMediaPath(trimmed);
205
233
  const cached = ctx.uploads.get(localPath);
206
234
  if (cached) return cached;
207
235
  const promise = ctx.uploads.size >= 10 ? (() => {
@@ -230,8 +258,8 @@ async function resolveOutboundMedia(refs, ctx, log, deps = {}) {
230
258
  attachments.push(resolved);
231
259
  continue;
232
260
  }
233
- if (isRemoteUrl(trimmed) || hasNonPathScheme(trimmed)) continue;
234
- const localPath = resolveLocalMediaPath(trimmed);
261
+ const localPath = toLocalUploadPath(trimmed);
262
+ if (!localPath) continue;
235
263
  if (!ctx.annotatedFailures.has(localPath)) {
236
264
  ctx.annotatedFailures.add(localPath);
237
265
  newFailures.push(basename(localPath));
@@ -327,11 +355,16 @@ async function rewriteLocalMarkdownImages(text, ctx, log, deps = {}) {
327
355
  const start = match.index;
328
356
  out += seg.text.slice(cursor, start);
329
357
  cursor = start + full.length;
330
- if (isRemoteUrl(src) || hasNonPathScheme(src)) {
358
+ if (isRemoteUrl(src)) {
359
+ out += full;
360
+ continue;
361
+ }
362
+ const localPath = toLocalUploadPath(src);
363
+ if (!localPath) {
331
364
  out += full;
332
365
  continue;
333
366
  }
334
- const ext = extensionOf(src.split("?")[0]);
367
+ const ext = extensionOf(localPath.split("?")[0]);
335
368
  if (!ext || !IMAGE_EXTENSIONS.has(ext)) {
336
369
  out += full;
337
370
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfe.ai/openclaw-chat",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "description": "OpenClaw chat plugin for Alfe — web widget and mobile app channels",
5
5
  "type": "module",
6
6
  "main": "./dist/plugin.js",
@@ -28,8 +28,8 @@
28
28
  ],
29
29
  "dependencies": {
30
30
  "@alfe.ai/agent-api-client": "^0.8.0",
31
- "@alfe.ai/chat": "^0.0.14",
32
- "@alfe.ai/config": "^0.3.0"
31
+ "@alfe.ai/config": "^0.3.0",
32
+ "@alfe.ai/chat": "^0.0.14"
33
33
  },
34
34
  "peerDependencies": {
35
35
  "openclaw": ">=2026.3.0"