// 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 SDK — 交付物上报核心 * * 零依赖(使用 Node ≥ 18 内置 fetch + fs) * 所有凭据从 env → config 文件 → 兜底,按高兼容链解析 */ import { readFileSync, existsSync, constants } from 'fs'; import { homedir, hostname } from 'os'; import { join } from 'path'; import { env } from 'process'; const HOME = homedir(); const GATEWAY = env.FMODE_API || 'https://server.fmode.cn'; const FN_ID = env.FN_DELIVERABLES || 'lfYlgU7SkK'; // ============================================================ // Agent 身份高兼容解析 // ============================================================ export function resolveAgentId() { // 1. env if (env.FMODE_AGENT_ID) return env.FMODE_AGENT_ID; if (env.AGENT_ID) return env.AGENT_ID; // 2. ~/.fmode/config.json const homeCfg = tryReadJSON(join(HOME, '.fmode', 'config.json')); if (homeCfg?.agentId) return homeCfg.agentId; // 3. ./.fmode/config.json (cwd) const cwdCfg = tryReadJSON(join(process.cwd(), '.fmode', 'config.json')); if (cwdCfg?.agentId) return cwdCfg.agentId; // 4. 兜底 hostname(仅 warn) const hn = hostname(); console.warn(`[warn] 未找到语义 agentId,回退到 hostname "${hn}"(可能不是注册名)`); return hn; } // ============================================================ // Session Token 高兼容解析 // ============================================================ export function resolveSessionToken() { // 1. env if (env.FMODE_SESSION_TOKEN) return env.FMODE_SESSION_TOKEN; // 2. ~/.fmode/config/user.json const userCfg = tryReadJSON(join(HOME, '.fmode', 'config', 'user.json')); if (userCfg?.sessionToken) return userCfg.sessionToken; // 3. ~/.fmode/config.json const homeCfg = tryReadJSON(join(HOME, '.fmode', 'config.json')); if (homeCfg?.sessionToken) return homeCfg.sessionToken; // 4. ./.fmode/config.json (cwd) const cwdCfg = tryReadJSON(join(process.cwd(), '.fmode', 'config.json')); if (cwdCfg?.sessionToken) return cwdCfg.sessionToken; return null; } // ============================================================ // 云函数调用 // ============================================================ export async function callFn(params) { const token = resolveSessionToken(); if (!token) throw new Error('无 sessionToken:无法调用云函数。设置 FMODE_SESSION_TOKEN 环境变量或 ~/.fmode/config/user.json'); const resp = await fetch(`${GATEWAY}/api/functions`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token, id: FN_ID, params }), }); return resp.json(); } // ============================================================ // 上报交付物 // ============================================================ export async function report({ agentId, agentObjectId, title, summary, project, artifacts, tags }) { if (!title) throw new Error('title 必填'); const resolvedAgentId = agentId || resolveAgentId(); if (!resolvedAgentId) throw new Error('agentId 必填'); return callFn({ action: 'report', agentId: resolvedAgentId, agentObjectId: agentObjectId || '', title, summary: summary || '', project: project || '', artifacts: artifacts || [], tags: tags || [], }); } // ============================================================ // 查询交付物 // ============================================================ export async function list({ limit = 5, agentId } = {}) { const r = await callFn({ action: 'list', agentId: agentId || resolveAgentId(), limit }); return r.deliverables || []; } // ============================================================ // 工具 // ============================================================ function tryReadJSON(p) { try { if (!existsSync(p)) return null; return JSON.parse(readFileSync(p, 'utf-8')); } catch { return null; } } export default { resolveAgentId, resolveSessionToken, report, list, callFn };