ofiere-openclaw-plugin 4.15.0 → 4.16.0
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/package.json +1 -1
- package/src/tools.ts +67 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ofiere-openclaw-plugin",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.16.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenClaw plugin for Ofiere PM - 11 meta-tools covering tasks, agents, projects, scheduling, knowledge, workflows, notifications, memory, prompts, constellation, and space file management",
|
|
6
6
|
"keywords": ["openclaw", "ofiere", "project-management", "agents", "plugin"],
|
package/src/tools.ts
CHANGED
|
@@ -3234,19 +3234,62 @@ function registerFileOps(
|
|
|
3234
3234
|
|
|
3235
3235
|
// ── File Ops helpers ─────────────────────────────────────────────────────────
|
|
3236
3236
|
|
|
3237
|
+
/** MIME types allowed by the space-files storage bucket. */
|
|
3238
|
+
const ALLOWED_STORAGE_MIMES = new Set([
|
|
3239
|
+
"image/png", "image/jpeg", "image/gif", "image/webp", "image/svg+xml",
|
|
3240
|
+
"video/mp4", "video/webm", "video/quicktime",
|
|
3241
|
+
"audio/mpeg", "audio/wav", "audio/ogg",
|
|
3242
|
+
"application/pdf", "text/plain", "text/markdown", "text/csv",
|
|
3243
|
+
"text/html", "text/css", "application/json",
|
|
3244
|
+
"application/javascript", "text/javascript", "application/typescript",
|
|
3245
|
+
"application/zip", "application/x-tar", "application/gzip",
|
|
3246
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
3247
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
3248
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
3249
|
+
"application/octet-stream",
|
|
3250
|
+
]);
|
|
3251
|
+
|
|
3252
|
+
/**
|
|
3253
|
+
* Simple FNV-1a 32-bit hash → 8-char hex string.
|
|
3254
|
+
* Used to generate stable, short slugs from non-ASCII filenames.
|
|
3255
|
+
*/
|
|
3256
|
+
function fnv1aHash(str: string): string {
|
|
3257
|
+
let hash = 0x811c9dc5;
|
|
3258
|
+
for (let i = 0; i < str.length; i++) {
|
|
3259
|
+
hash ^= str.charCodeAt(i);
|
|
3260
|
+
hash = (hash * 0x01000193) >>> 0;
|
|
3261
|
+
}
|
|
3262
|
+
return hash.toString(16).padStart(8, "0");
|
|
3263
|
+
}
|
|
3264
|
+
|
|
3237
3265
|
/**
|
|
3238
3266
|
* Sanitize a filename for safe use as a Supabase storage object key.
|
|
3239
|
-
* Strips characters that break storage APIs
|
|
3240
|
-
*
|
|
3267
|
+
* Strips characters that break storage APIs while preserving extension and readability.
|
|
3268
|
+
* For Unicode-heavy filenames, generates a hash-based slug to maintain uniqueness.
|
|
3269
|
+
* The original filename is always kept in DB metadata.
|
|
3241
3270
|
*/
|
|
3242
3271
|
function sanitizeStorageFileName(fileName: string): string {
|
|
3243
|
-
//
|
|
3244
|
-
|
|
3272
|
+
// Split extension from stem
|
|
3273
|
+
const dotIdx = fileName.lastIndexOf(".");
|
|
3274
|
+
const stem = dotIdx > 0 ? fileName.slice(0, dotIdx) : fileName;
|
|
3275
|
+
const ext = dotIdx > 0 ? fileName.slice(dotIdx) : ""; // includes dot
|
|
3276
|
+
|
|
3277
|
+
// Sanitize stem: spaces → hyphens, strip non-ASCII/non-safe chars
|
|
3278
|
+
let safeStem = stem
|
|
3245
3279
|
.replace(/\s+/g, "-")
|
|
3246
|
-
.replace(/[^a-zA-Z0-
|
|
3247
|
-
.replace(/-{2,}/g, "-")
|
|
3248
|
-
.replace(/^-+|-+$/g, "")
|
|
3249
|
-
|
|
3280
|
+
.replace(/[^a-zA-Z0-9_-]/g, "")
|
|
3281
|
+
.replace(/-{2,}/g, "-")
|
|
3282
|
+
.replace(/^-+|-+$/g, "");
|
|
3283
|
+
|
|
3284
|
+
// If stem collapsed (e.g. all Unicode), generate hash-based slug for traceability
|
|
3285
|
+
if (safeStem.length < 3) {
|
|
3286
|
+
safeStem = `f-${fnv1aHash(stem)}`;
|
|
3287
|
+
}
|
|
3288
|
+
|
|
3289
|
+
// Sanitize extension too (should be safe but belt-and-suspenders)
|
|
3290
|
+
const safeExt = ext.replace(/[^a-zA-Z0-9.]/g, "");
|
|
3291
|
+
|
|
3292
|
+
return `${safeStem}${safeExt}` || "file";
|
|
3250
3293
|
}
|
|
3251
3294
|
|
|
3252
3295
|
// ── File Ops handlers ────────────────────────────────────────────────────────
|
|
@@ -3408,6 +3451,7 @@ async function handleUploadFile(
|
|
|
3408
3451
|
|
|
3409
3452
|
// Auto-detect MIME from extension if not explicitly provided
|
|
3410
3453
|
let mimeType = params.file_type as string;
|
|
3454
|
+
let mimeWarning: string | undefined;
|
|
3411
3455
|
if (!mimeType) {
|
|
3412
3456
|
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
|
3413
3457
|
const binaryMimeMap: Record<string, string> = {
|
|
@@ -3428,6 +3472,10 @@ async function handleUploadFile(
|
|
|
3428
3472
|
yml: "text/yaml", xml: "text/xml",
|
|
3429
3473
|
};
|
|
3430
3474
|
mimeType = binaryMimeMap[ext] || "application/octet-stream";
|
|
3475
|
+
} else if (!ALLOWED_STORAGE_MIMES.has(mimeType)) {
|
|
3476
|
+
// Explicit MIME not in bucket allowlist → fall back to octet-stream to prevent upload rejection
|
|
3477
|
+
mimeWarning = `Requested MIME "${mimeType}" is not in storage allowlist — stored as application/octet-stream. The original type is preserved in file metadata.`;
|
|
3478
|
+
mimeType = "application/octet-stream";
|
|
3431
3479
|
}
|
|
3432
3480
|
|
|
3433
3481
|
const safeFileName = sanitizeStorageFileName(fileName);
|
|
@@ -3439,6 +3487,10 @@ async function handleUploadFile(
|
|
|
3439
3487
|
|
|
3440
3488
|
if (uploadErr) return err(`Storage upload failed: ${uploadErr.message}`);
|
|
3441
3489
|
|
|
3490
|
+
// Store the originally-requested MIME type in DB (for downstream consumers),
|
|
3491
|
+
// but use the storage-safe MIME for the actual upload
|
|
3492
|
+
const dbMimeType = (params.file_type as string) || mimeType;
|
|
3493
|
+
|
|
3442
3494
|
const { data, error } = await supabase
|
|
3443
3495
|
.from("pm_space_files")
|
|
3444
3496
|
.insert({
|
|
@@ -3446,7 +3498,7 @@ async function handleUploadFile(
|
|
|
3446
3498
|
space_id: spaceId,
|
|
3447
3499
|
folder_id: (params.folder_id as string) || null,
|
|
3448
3500
|
file_name: fileName,
|
|
3449
|
-
file_type:
|
|
3501
|
+
file_type: dbMimeType,
|
|
3450
3502
|
file_size: bytes.length,
|
|
3451
3503
|
storage_path: storagePath,
|
|
3452
3504
|
is_shared: false,
|
|
@@ -3456,7 +3508,12 @@ async function handleUploadFile(
|
|
|
3456
3508
|
.single();
|
|
3457
3509
|
|
|
3458
3510
|
if (error) return err(error.message);
|
|
3459
|
-
|
|
3511
|
+
const result: Record<string, unknown> = {
|
|
3512
|
+
message: `File "${fileName}" uploaded (${bytes.length} bytes)`,
|
|
3513
|
+
file: data,
|
|
3514
|
+
};
|
|
3515
|
+
if (mimeWarning) result.warning = mimeWarning;
|
|
3516
|
+
return ok(result);
|
|
3460
3517
|
} catch (e) { return err(e instanceof Error ? e.message : String(e)); }
|
|
3461
3518
|
}
|
|
3462
3519
|
|