|
|
@@ -0,0 +1,115 @@
|
|
|
+/**
|
|
|
+ * 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 };
|