deliverable-report-v2.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. // Copyright (c) 未来飞马
  2. //
  3. // Licensed under the MIT License. See LICENSE in the project root
  4. // for the full license text.
  5. //
  6. // Trademark Notice:
  7. // The MIT license grants copyright permissions for source code only.
  8. // It does NOT grant any rights to use trademarks including "未来飞马",
  9. // "Harness Loop", "RSI", and associated slogan "让AI进化提前发生,让AI落地快人一步".
  10. // Any use of these trademarks requires separate written permission.
  11. /**
  12. * 交付物上报云函数(skill-deliverable 配套)
  13. * ============================================
  14. *
  15. * 部署:Parse.Cloud.define 或 /api/functions 模式
  16. * 云函数 ID:lfYlgU7SkK(保持不变)
  17. *
  18. * v2 变更:
  19. * 1. 移除 FMODE_AGENT_SUPERADMIN 角色检查 → 改为:用户仅对自有容器有上报权限
  20. * 2. agentId → agentObjectId 自动解析(传任一即可,缺失则自动补全)
  21. * 3. 首次上报时自动创建 FmodeAgent 记录(自注册)
  22. * 4. 参数缺失时自动补全(时间戳、状态等)
  23. *
  24. * 鉴权逻辑(简化版):
  25. * sessionToken → _User → 查此用户是否是该 FmodeAgent 记录的 owner
  26. * 是 → 允许上报;否 → 403
  27. * 若 FmodeAgent 记录不存在且 agentId 有效 → 自动创建 + 设当前用户为 owner
  28. */
  29. const CLASS_DELIVERABLE = 'AgentDeliverable';
  30. const CLASS_AGENT = 'FmodeAgent';
  31. Parse.Cloud.define('deliverable-report', async (req) => {
  32. const { agentId, agentObjectId, title, summary, project, artifacts, tags } = req.params;
  33. const user = req.user;
  34. if (!user) throw new Parse.Error(401, '需要登录');
  35. if (!title) throw new Parse.Error(400, 'title 必填');
  36. // ---- Step 1: 解析/注册 FmodeAgent ----
  37. let agentQuery = new Parse.Query(CLASS_AGENT);
  38. if (agentObjectId) {
  39. agentQuery.equalTo('objectId', agentObjectId);
  40. } else if (agentId) {
  41. agentQuery.equalTo('agentId', agentId);
  42. } else {
  43. throw new Parse.Error(400, 'agentId 或 agentObjectId 必填');
  44. }
  45. let agent = await agentQuery.first({ useMasterKey: true });
  46. // 自动注册:agent 不存在则创建
  47. if (!agent) {
  48. if (!agentId) throw new Parse.Error(400, '新 agent 注册需要 agentId');
  49. const AgentClass = Parse.Object.extend(CLASS_AGENT);
  50. agent = new AgentClass();
  51. agent.set('agentId', agentId);
  52. agent.set('user', user);
  53. agent.set('status', 'online');
  54. agent.set('heartbeatAt', new Date());
  55. agent = await agent.save(null, { useMasterKey: true });
  56. }
  57. const finalAgentId = agent.get('agentId') || agentId;
  58. const finalAgentOid = agent.id;
  59. // ---- Step 2: 权限校验(仅校验用户是否为 agent owner)----
  60. const agentUser = agent.get('user');
  61. if (agentUser && agentUser.id !== user.id) {
  62. throw new Parse.Error(403, '仅该容器的 owner 可上报');
  63. }
  64. // 如果 agent 没有 owner(旧数据),自动绑定当前用户
  65. if (!agentUser) {
  66. agent.set('user', user);
  67. await agent.save(null, { useMasterKey: true });
  68. }
  69. // ---- Step 3: 写入交付物 ----
  70. const DelClass = Parse.Object.extend(CLASS_DELIVERABLE);
  71. const record = new DelClass();
  72. record.set('agentId', finalAgentId);
  73. record.set('agent', agent); // 指针
  74. record.set('agentObjectId', finalAgentOid);
  75. record.set('user', user);
  76. record.set('title', title);
  77. record.set('summary', summary || '');
  78. record.set('project', project || '');
  79. record.set('artifacts', artifacts || []);
  80. record.set('tags', tags || []);
  81. record.set('reportedAt', new Date());
  82. // ACL:仅 owner 可读写
  83. const acl = new Parse.ACL(user);
  84. acl.setPublicReadAccess(false);
  85. acl.setPublicWriteAccess(false);
  86. record.setACL(acl);
  87. const saved = await record.save(null, { useMasterKey: true });
  88. // ---- Step 4: 更新 agent 心跳 ----
  89. agent.set('heartbeatAt', new Date());
  90. agent.set('status', 'online');
  91. await agent.save(null, { useMasterKey: true });
  92. return {
  93. code: 200,
  94. id: saved.id,
  95. agentId: finalAgentId,
  96. agentObjectId: finalAgentOid,
  97. title: title,
  98. createdAt: saved.get('createdAt') || saved.createdAt,
  99. };
  100. });