Skip to content

fix/nanda array types and live agent publish - #4

Merged
Priyank911 merged 5 commits into
mainfrom
fix/nanda-array-types-and-live-agent-publish
Aug 2, 2026
Merged

fix/nanda array types and live agent publish#4
Priyank911 merged 5 commits into
mainfrom
fix/nanda-array-types-and-live-agent-publish

Conversation

@Anarv2104

Copy link
Copy Markdown
Collaborator
  • Parse capabilities/tags as real arrays before sending to NANDA
  • Add Cloudinary asset storage with signed authenticated delivery
  • Add seller upload wizard with AI metadata review
  • Fix search fallback when AI/NANDA search path returns no results
  • Document Node v26.5.x runtime requirement

Anarv2104 and others added 5 commits August 2, 2026 21:03
buildAgentFacts() was passing listing.capabilities/tags straight
through from SQLite, where they're stored as JSON-encoded strings
(e.g. "[\"code-review\",...]"), not real arrays. NANDA's index was
storing and returning a string that merely looks like an array.

Adds safeParseArray(): parses the stored JSON string into a real
array, passes through unchanged if already an array, and falls back
to [] on anything empty or unparseable.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- processUpload streams uploaded files to Cloudinary via
  resource_type: 'raw', type: 'authenticated' (private, not public),
  storing only the public_id (not a permanent URL) in stored_path.
  Falls back to local disk if Cloudinary credentials are missing,
  matching the existing fallback pattern used by Prava/Linq/NANDA.
- prepareDelivery/getDeliveryPayload now self-fetch assets by
  listingId and generate a fresh, short-expiry (5 min) signed
  Cloudinary URL at the moment of delivery, after the caller's
  payment gate has already passed, instead of a permanent link
  computed once at upload time.
- Fixed multer in listings.js to use memoryStorage() instead of disk
  storage — processUpload has always assumed req.file.buffer exists,
  which disk storage never populates.
- Added cloudinary dependency and CLOUDINARY_* placeholders to
  .env.example.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Real create -> upload -> generate-metadata -> review/edit -> publish
flow in SellerPortal.jsx, replacing the old hardcoded
capabilities: ['code_review', 'automation'] and the flow that
published a listing without ever calling /upload or
/generate-metadata:

- Drag-and-drop file zone (.zip/.doc/.docx/.txt/.md) for static
  listings, wired into the real upload sequence via a raw
  fetch + FormData call (api.js's apiRequest is JSON-only, can't
  carry a file).
- New MetadataReviewModal shows the AI-derived category/
  capabilities/tags for the seller to confirm or edit before they're
  persisted via PUT and the listing is published — visibly shows the
  AI's real output instead of auto-publishing blind.
- api.js now exports API_BASE for the raw upload fetch call.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A teammate's commit added a Weft-Agent/NANDA semantic search path in
marketplace.js's /search route, tried ahead of the existing FTS
fallback. Two bugs combined to silently return empty search results
whenever OPENAI_API_KEY was unset or left at its placeholder value:

- weft-agent.js's aiSearchNanda() only checked truthiness of
  OPENAI_API_KEY, so the literal placeholder string counted as
  "configured" (unlike llm-client.js's existing correct check
  elsewhere in this codebase, which explicitly excludes it).
- Even when aiSearchNanda failed internally, it caught the error and
  returned [] instead of throwing, so marketplace.js's FTS fallback
  (which only triggered on a thrown exception) never ran.

Fixes: weft-agent.js now excludes the placeholder key, matching
llm-client.js's pattern. marketplace.js now falls back to FTS
whenever aiSearchNanda produces zero results, not only when it
throws.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
better-sqlite3's native binary has a confirmed ABI conflict under
Node v20.18.3 on this stack: loading it crashes with EXC_BAD_ACCESS
inside napi_module_register_by_symbol (confirmed with lldb). The
crash reproduces identically whether using the registry's prebuilt
binary or rebuilding from source against the exact v20.18.3 headers,
so this isn't a build mistake — v26.5.x loads it cleanly and is what
this project now targets.

Also documents that .mcp.json (gitignored) belongs at the repo's own
root, not a nested subfolder, after an earlier session lost time to
edits landing in an unused duplicate one level down.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 2, 2026 15:40

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves NANDA integration correctness (arrays + search fallback), introduces optional Cloudinary-backed asset storage with signed delivery, and adds a seller-side upload + AI metadata review flow to support publishing static listings.

Changes:

  • Parse capabilities/tags as real arrays when publishing agent facts to NANDA, and improve AI search fallback to local FTS when no results are returned.
  • Add Cloudinary “authenticated” raw asset uploads and short-lived signed download URLs at delivery time (fallback to local disk when not configured).
  • Add a seller upload wizard for static listings, including AI metadata generation and a confirmation modal before publishing.

Reviewed changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/services/weft-agent.js Treat placeholder OpenAI keys as invalid to avoid misleading configuration.
src/services/nanda.js Ensure capabilities/tags are sent to NANDA as arrays even when stored as JSON strings.
src/services/asset-processor.js Add Cloudinary upload/delivery path and signed URL generation for assets.
src/routes/marketplace.js Fall back to FTS when AI/NANDA search returns an empty result set (not only on throw).
src/routes/listings.js Switch uploads to in-memory buffers to support Cloudinary streaming uploads.
README.md Document Node v26.5.x runtime requirement and rationale.
package.json Add cloudinary dependency.
package-lock.json Lock dependency updates for Cloudinary and transitive deps.
frontend/src/components/SellerPortal.jsx Add seller upload + AI metadata review publishing flow for static listings; publish live listings directly.
frontend/src/components/MetadataReviewModal.jsx New UI for reviewing/editing AI-generated metadata before publish.
frontend/src/api.js Export API_BASE for use by direct fetch upload helper.
.env.example Document Cloudinary environment variables.
Suppressed comments (1)

src/services/asset-processor.js:97

  • The delivered/manifest path for non-zip uploads currently uses file.originalname verbatim. If that name contains path separators, consumers that save/extract using this path can be exposed to path traversal / zip-slip style issues. Use the same sanitized filename used for storage.
      manifest = [{
        relativePath: file.originalname,
        role: 'main',
        sizeBytes: file.size,

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +60 to 78
const isZip = file.originalname.toLowerCase().endsWith('.zip');
let storedPath;
let storage;

if (cloudinaryEnabled) {
const publicId = `weft/${listingId}/${uuidv4()}_${file.originalname}`;
const result = await uploadToCloudinary(file.buffer, publicId);
storedPath = result.public_id; // signed fresh at delivery time, never a stored permanent URL
storage = 'cloudinary';
} else {
const listingDir = path.join(UPLOADS_DIR, listingId);
if (!fs.existsSync(listingDir)) {
fs.mkdirSync(listingDir, { recursive: true });
}
const storedFileName = `${uuidv4()}_${file.originalname}`;
storedPath = path.join(listingDir, storedFileName);
fs.writeFileSync(storedPath, file.buffer);
storage = 'local';
}
Comment on lines 144 to 146
if (asset.manifest_json) {
completeManifest = completeManifest.concat(typeof asset.manifest_json === 'string' ? JSON.parse(asset.manifest_json) : asset.manifest_json);
}
Comment on lines +148 to +152
if (cloudinaryEnabled && isCloudinaryPublicId(asset.stored_path)) {
// Generated here, at the moment of delivery — after the caller's payment
// gate has already passed — so it's never a permanent link computed once
// at upload time. Short TTL instead of embedding buyer context: Cloudinary's
// signed-URL API has no field for arbitrary audit metadata.
Comment thread src/routes/listings.js

const router = Router();
const upload = multer({ dest: './uploads/temp' });
const upload = multer({ storage: multer.memoryStorage() });
Comment on lines +15 to 17
if (!process.env.OPENAI_API_KEY || process.env.OPENAI_API_KEY === 'sk-your-openai-key') {
throw new Error('OPENAI_API_KEY is not set');
}
Comment on lines +24 to +30
<div style={{ position: 'fixed', inset: 0, zIndex: 1000, background: 'rgba(0,0,0,0.85)', backdropFilter: 'blur(10px)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '20px' }}>
<div style={{ background: 'var(--bg-card)', border: '1px solid var(--border-strong)', borderRadius: 'var(--radius-lg)', padding: '32px', maxWidth: '520px', width: '100%', boxShadow: 'var(--shadow-card)' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '8px' }}>
<h3 style={{ fontSize: '1.25rem', fontWeight: 700, display: 'flex', alignItems: 'center', gap: '8px' }}>
<Sparkles size={20} color="var(--accent-brand)" /> Review AI-Generated Metadata
</h3>
<button onClick={onCancel} style={{ background: 'none', border: 'none', color: 'var(--text-secondary)', cursor: 'pointer' }}><X size={20} /></button>
Comment on lines +654 to +658
<button
type="button"
onClick={(e) => { e.stopPropagation(); setUploadFile(null); }}
style={{ background: 'none', border: 'none', color: 'var(--text-secondary)', cursor: 'pointer' }}
>
@Priyank911
Priyank911 merged commit 6f80b10 into main Aug 2, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants