* Publish a Gene as a Skill to the Hub skill store. * * @param {object} gene - Gene asset * @param {object} [opts] - { category, tags } * @returns {Promise<{ok: boolean, result?: object, error?: string}>}
(gene, opts)
| 249 | * @returns {Promise<{ok: boolean, result?: object, error?: string}>} |
| 250 | */ |
| 251 | function publishSkillToHub(gene, opts) { |
| 252 | opts = opts || {}; |
| 253 | var hubUrl = getHubUrl(); |
| 254 | if (!hubUrl) return Promise.resolve({ ok: false, error: 'no_hub_url' }); |
| 255 | |
| 256 | // Shallow-copy gene to avoid mutating the caller's object |
| 257 | var geneCopy = {}; |
| 258 | Object.keys(gene).forEach(function (k) { geneCopy[k] = gene[k]; }); |
| 259 | if (Array.isArray(geneCopy.signals_match)) { |
| 260 | try { |
| 261 | var distiller = require('./skillDistiller'); |
| 262 | geneCopy.signals_match = distiller.sanitizeSignalsMatch(geneCopy.signals_match); |
| 263 | } catch (e) { /* distiller not available, skip */ } |
| 264 | } |
| 265 | |
| 266 | var content = geneToSkillMd(geneCopy); |
| 267 | var nodeId = getNodeId(); |
| 268 | var fmName = content.match(/^name:\s*(.+)$/m); |
| 269 | var derivedName = fmName ? fmName[1].trim().toLowerCase().replace(/[^a-z0-9]+/g, '_') : (gene.id || 'unnamed').replace(/^gene_/, ''); |
| 270 | // Strip ALL embedded timestamps from skillId |
| 271 | derivedName = derivedName.replace(/_?\d{10,}_?/g, '_').replace(/_+/g, '_').replace(/^_|_$/g, ''); |
| 272 | var skillId = 'skill_' + derivedName; |
| 273 | |
| 274 | // Clean tags: use already-sanitized signals from geneCopy |
| 275 | var tags = opts.tags || geneCopy.signals_match || []; |
| 276 | tags = tags.filter(function (t) { |
| 277 | var s = String(t || '').trim(); |
| 278 | return s.length >= 3 && !/^\d+$/.test(s) && !/\d{10,}/.test(s); |
| 279 | }); |
| 280 | |
| 281 | var body = { |
| 282 | sender_id: nodeId, |
| 283 | skill_id: skillId, |
| 284 | content: content, |
| 285 | category: opts.category || geneCopy.category || null, |
| 286 | tags: tags, |
| 287 | }; |
| 288 | |
| 289 | var endpoint = hubUrl.replace(/\/+$/, '') + '/a2a/skill/store/publish'; |
| 290 | |
| 291 | return hubFetch(endpoint, { |
| 292 | method: 'POST', |
| 293 | headers: buildSkillStoreHeaders(), |
| 294 | body: JSON.stringify(body), |
| 295 | signal: AbortSignal.timeout(15000), |
| 296 | }) |
| 297 | .then(function (res) { return res.json().then(function (data) { return { status: res.status, data: data }; }); }) |
| 298 | .then(function (result) { |
| 299 | if (result.status === 201 || result.status === 200) { |
| 300 | return { ok: true, result: result.data }; |
| 301 | } |
| 302 | if (result.status === 409) { |
| 303 | return updateSkillOnHub(nodeId, skillId, content, opts, gene); |
| 304 | } |
| 305 | return { ok: false, error: result.data?.error || 'publish_failed', status: result.status }; |
| 306 | }) |
| 307 | .catch(function (err) { |
| 308 | return { ok: false, error: err.message }; |
nothing calls this directly
no test coverage detected