// Copyright (c) 未来飞马 // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. // // Trademark Notice: // The MPL-2.0 license grants copyright permissions for source code only. // It does NOT grant any rights to use trademarks including "未来飞马", // "Harness Loop", "RSI", and associated slogan "让AI进化提前发生,让AI落地快人一步". // Any use of these trademarks requires separate written permission. /** * 交付物上报云函数(skill-deliverable 配套) * ============================================ * * 部署:Parse.Cloud.define 或 /api/functions 模式 * 云函数 ID:lfYlgU7SkK(保持不变) * * v2 变更: * 1. 移除 FMODE_AGENT_SUPERADMIN 角色检查 → 改为:用户仅对自有容器有上报权限 * 2. agentId → agentObjectId 自动解析(传任一即可,缺失则自动补全) * 3. 首次上报时自动创建 FmodeAgent 记录(自注册) * 4. 参数缺失时自动补全(时间戳、状态等) * * 鉴权逻辑(简化版): * sessionToken → _User → 查此用户是否是该 FmodeAgent 记录的 owner * 是 → 允许上报;否 → 403 * 若 FmodeAgent 记录不存在且 agentId 有效 → 自动创建 + 设当前用户为 owner */ const CLASS_DELIVERABLE = 'AgentDeliverable'; const CLASS_AGENT = 'FmodeAgent'; Parse.Cloud.define('deliverable-report', async (req) => { const { agentId, agentObjectId, title, summary, project, artifacts, tags } = req.params; const user = req.user; if (!user) throw new Parse.Error(401, '需要登录'); if (!title) throw new Parse.Error(400, 'title 必填'); // ---- Step 1: 解析/注册 FmodeAgent ---- let agentQuery = new Parse.Query(CLASS_AGENT); if (agentObjectId) { agentQuery.equalTo('objectId', agentObjectId); } else if (agentId) { agentQuery.equalTo('agentId', agentId); } else { throw new Parse.Error(400, 'agentId 或 agentObjectId 必填'); } let agent = await agentQuery.first({ useMasterKey: true }); // 自动注册:agent 不存在则创建 if (!agent) { if (!agentId) throw new Parse.Error(400, '新 agent 注册需要 agentId'); const AgentClass = Parse.Object.extend(CLASS_AGENT); agent = new AgentClass(); agent.set('agentId', agentId); agent.set('user', user); agent.set('status', 'online'); agent.set('heartbeatAt', new Date()); agent = await agent.save(null, { useMasterKey: true }); } const finalAgentId = agent.get('agentId') || agentId; const finalAgentOid = agent.id; // ---- Step 2: 权限校验(仅校验用户是否为 agent owner)---- const agentUser = agent.get('user'); if (agentUser && agentUser.id !== user.id) { throw new Parse.Error(403, '仅该容器的 owner 可上报'); } // 如果 agent 没有 owner(旧数据),自动绑定当前用户 if (!agentUser) { agent.set('user', user); await agent.save(null, { useMasterKey: true }); } // ---- Step 3: 写入交付物 ---- const DelClass = Parse.Object.extend(CLASS_DELIVERABLE); const record = new DelClass(); record.set('agentId', finalAgentId); record.set('agent', agent); // 指针 record.set('agentObjectId', finalAgentOid); record.set('user', user); record.set('title', title); record.set('summary', summary || ''); record.set('project', project || ''); record.set('artifacts', artifacts || []); record.set('tags', tags || []); record.set('reportedAt', new Date()); // ACL:仅 owner 可读写 const acl = new Parse.ACL(user); acl.setPublicReadAccess(false); acl.setPublicWriteAccess(false); record.setACL(acl); const saved = await record.save(null, { useMasterKey: true }); // ---- Step 4: 更新 agent 心跳 ---- agent.set('heartbeatAt', new Date()); agent.set('status', 'online'); await agent.save(null, { useMasterKey: true }); return { code: 200, id: saved.id, agentId: finalAgentId, agentObjectId: finalAgentOid, title: title, createdAt: saved.get('createdAt') || saved.createdAt, }; });