@bolloon/bolloon-agent 0.4.23 → 0.4.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/agents/execution-supervisor.js +391 -0
  2. package/dist/agents/goal-store.js +451 -0
  3. package/dist/agents/pi-harness.js +263 -0
  4. package/dist/agents/pi-sdk.js +568 -126
  5. package/dist/agents/run-store.js +772 -0
  6. package/dist/agents/runner-resolver.js +225 -0
  7. package/dist/agents/skills-manager.js +435 -0
  8. package/dist/agents/supervisor-host.js +249 -0
  9. package/dist/cron/tick-lock.js +1 -1
  10. package/dist/index.js +428 -22
  11. package/dist/ios/agent-delegate-server.js +58 -12
  12. package/dist/ios/icons/icon-1024x1024.png +0 -0
  13. package/dist/ios/icons/icon-1024x1024.webp +0 -0
  14. package/dist/ios/icons/icon-216x216.png +0 -0
  15. package/dist/ios/icons/icon-216x216.webp +0 -0
  16. package/dist/ios/index.html +95 -18
  17. package/dist/ios/manifest.json +1 -1
  18. package/dist/ios/mobile-agent.js +244 -5
  19. package/dist/ios/mobile-chain.js +481 -0
  20. package/dist/ios/mobile-core.js +25177 -24664
  21. package/dist/ios/mobile-helia.js +683 -0
  22. package/dist/ios/mobile-ipfs.js +680 -0
  23. package/dist/ios/mobile-orbit.js +268 -0
  24. package/dist/ios/mobile-p2p.js +130 -1
  25. package/dist/ios/mobile-social.js +624 -0
  26. package/dist/ios/mobile-sync.js +193 -0
  27. package/dist/ios/mobile-trade.js +492 -0
  28. package/dist/ios/mobile-wallet.js +11 -0
  29. package/dist/ios/mobile.css +39 -0
  30. package/dist/ios/mobile.html +95 -18
  31. package/dist/ios/mobile.js +1054 -55
  32. package/dist/ios/routes-x402-info.js +194 -0
  33. package/dist/ios/server.js +350 -9
  34. package/dist/ios/sw.js +26 -2
  35. package/dist/web/agent-delegate-server.js +58 -12
  36. package/dist/web/icons/icon-1024x1024.png +0 -0
  37. package/dist/web/icons/icon-1024x1024.webp +0 -0
  38. package/dist/web/icons/icon-216x216.png +0 -0
  39. package/dist/web/icons/icon-216x216.webp +0 -0
  40. package/dist/web/manifest.json +1 -1
  41. package/dist/web/mobile-agent.js +197 -3
  42. package/dist/web/mobile-core.js +16417 -16090
  43. package/dist/web/mobile-privacy.js +185 -0
  44. package/dist/web/mobile.css +15 -0
  45. package/dist/web/mobile.html +21 -1
  46. package/dist/web/mobile.js +179 -6
  47. package/dist/web/server.js +437 -4
  48. package/package.json +2 -2
@@ -1530,6 +1530,11 @@ async function getAgentForChannel(channelId, channelDid, channelName, channelDid
1530
1530
  usePivotLoop: true,
1531
1531
  }, true); // forceNew: true 强制创建新实例
1532
1532
  channelSessions.set(sessionKey, session);
1533
+ // 2026-09-16: 标记运行表面 — 落盘 run 记录里能区分 web 运行 (CLI 默认 cli)
1534
+ try {
1535
+ session.setRunSurface?.('web');
1536
+ }
1537
+ catch { /* 老 session 无此方法则忽略 */ }
1533
1538
  if (channelDid) {
1534
1539
  console.log(`[Agent] 新建频道 ${channelId} session, DID = ${channelDid}, sessionId = ${currentSessionId}`);
1535
1540
  }
@@ -1626,6 +1631,92 @@ export async function createWebServer(port = 3000, options = {}) {
1626
1631
  catch (err) {
1627
1632
  console.warn('[createWebServer] bootstrap 失败 (非致命):', err);
1628
1633
  }
1634
+ // 2026-09-16: 孤儿运行对账 — 上次进程留下的 running 记录改判 interrupted。
1635
+ // 不做这步的话, 刷新/重启后 UI 会显示"还在跑"的幽灵运行 (最典型的假状态)。
1636
+ try {
1637
+ const { reconcileOrphans } = await import('../agents/run-store.js');
1638
+ const rec = await reconcileOrphans();
1639
+ if (rec.interrupted.length) {
1640
+ console.log(`[runs] 对账: ${rec.interrupted.length} 条上次中断的运行已标 interrupted (${rec.interrupted.slice(0, 3).join(', ')}${rec.interrupted.length > 3 ? '…' : ''})`);
1641
+ }
1642
+ if (rec.stillRunning.length)
1643
+ console.log(`[runs] 存活运行: ${rec.stillRunning.length} 条`);
1644
+ }
1645
+ catch (err) {
1646
+ console.warn('[runs] 对账失败 (非致命):', err?.message);
1647
+ }
1648
+ // 2026-09-16 (M2-B): ExecutionSupervisor —— 长期执行层。
1649
+ // 页面关掉、进程重启后由它把 Goal 继续下去; 跨进程排他靠 Goal lease (不是内存状态)。
1650
+ // Harness 管"这一段能不能安全执行", Supervisor 管"这个目标还要不要继续执行"。
1651
+ if (process.env.BOLLOON_SUPERVISOR !== '0') {
1652
+ try {
1653
+ const { getSupervisor } = await import('../agents/execution-supervisor.js');
1654
+ const { runSupervisorHost } = await import('../agents/supervisor-host.js');
1655
+ const tickMs = Number(process.env.BOLLOON_SUPERVISOR_TICK_MS) || 30_000;
1656
+ const leaseMs = Number(process.env.BOLLOON_SUPERVISOR_LEASE_MS) || 90_000;
1657
+ // 执行器**在每次执行前解析** (不写死): web 宿主按 Goal 的 channelId 找 channel agent。
1658
+ // 解析不出来 → 只诊断不执行, 且不写任何 Goal 状态。
1659
+ const resolver = async (req) => {
1660
+ const goal = req.goal;
1661
+ if (!goal?.channelId) {
1662
+ return { ok: false, kind: 'none', reason: 'Goal 没有 channelId: 无法在 web 进程内解析执行器' };
1663
+ }
1664
+ const agent = await getAgentForChannel(goal.channelId, goal.agentId || '', undefined, undefined);
1665
+ if (!agent)
1666
+ return { ok: false, kind: 'none', reason: 'channel agent 不可用' };
1667
+ const runner = async (r) => {
1668
+ if (r.kind === 'resume' && r.prevRunId && typeof agent.resumeRun === 'function') {
1669
+ const res = await agent.resumeRun(r.prevRunId);
1670
+ return { runId: r.prevRunId, status: res?.ok ? 'done' : 'failed', error: res?.ok ? undefined : res?.reason };
1671
+ }
1672
+ // 跨预算继续: 新 Run 必须挂在同一个 Goal 下 (setGoalId), 并带上上一个 Run 的非幂等守卫
1673
+ agent.setGoalId?.(goal.goalId);
1674
+ agent.setContinuationGuards?.(r.guards || []);
1675
+ const reply = await agent.prompt(r.instruction);
1676
+ // 注意: prompt 收尾会清空 currentRunId → 必须读 lastRunId (否则会拿上一条 run 做决策)
1677
+ const runId = agent.getLastRunId?.() || agent.getRunId?.();
1678
+ return { runId, status: 'done', reply: typeof reply === 'string' ? reply.slice(0, 500) : undefined };
1679
+ };
1680
+ return { ok: true, runner, kind: 'web' };
1681
+ };
1682
+ const sup = getSupervisor({
1683
+ tickIntervalMs: tickMs,
1684
+ leaseTtlMs: leaseMs,
1685
+ maxPerTick: Number(process.env.BOLLOON_SUPERVISOR_MAX_PER_TICK) || 1,
1686
+ log: (m) => console.log(m),
1687
+ onEvent: (e) => { try {
1688
+ broadcast({ type: 'supervisor', ...e });
1689
+ }
1690
+ catch { /* UI 广播失败不影响调度 */ } },
1691
+ resolver: resolver,
1692
+ });
1693
+ // 宿主层: 跨进程单 tick 互斥 + 宿主身份/心跳落盘 + 优雅停止
1694
+ const host = await runSupervisorHost({
1695
+ supervisor: sup,
1696
+ tickIntervalMs: tickMs,
1697
+ leaseTtlMs: leaseMs,
1698
+ runnerKind: 'web',
1699
+ log: (m) => console.log(m),
1700
+ });
1701
+ global.__bolloonSupervisorHost = host;
1702
+ console.log(`[supervisor] 长期执行层已启动 (owner=${host.state.owner}, worker=${host.state.workerId}, tick=${tickMs}ms, lease=${leaseMs}ms)`);
1703
+ const stopHost = (sig) => { void host.stop(`signal:${sig}`).finally(() => process.exit(0)); };
1704
+ process.once('SIGINT', () => stopHost('SIGINT'));
1705
+ process.once('SIGTERM', () => stopHost('SIGTERM'));
1706
+ }
1707
+ catch (err) {
1708
+ // 启动失败必须可观测 (不许静默降级成长时间不执行)
1709
+ const msg = `[supervisor] 启动失败: ${err?.message || err}`;
1710
+ console.error(msg);
1711
+ try {
1712
+ broadcast({ type: 'supervisor', kind: 'startup_failed', message: msg });
1713
+ }
1714
+ catch { /* ignore */ }
1715
+ }
1716
+ }
1717
+ else {
1718
+ console.log('[supervisor] 长期执行层已关闭 (BOLLOON_SUPERVISOR=0)');
1719
+ }
1629
1720
  // 2026-08-03 (Context OS P5): 初始化资产层 12+3 目录 (幂等, 每层 README 声明职责边界)
1630
1721
  try {
1631
1722
  const { ensureContextOsDirs } = await import('../bootstrap/context-os.js');
@@ -2439,6 +2530,19 @@ export async function createWebServer(port = 3000, options = {}) {
2439
2530
  console.log(`[cron] 调度器已启动 (每 ${Math.round((Number(process.env.BOLLOON_CRON_HEARTBEAT_MS) || 60_000) / 1000)}s 一轮, tick 带锁 + DND)`);
2440
2531
  // 暴露给 /message 使用 (主任务闸门)
2441
2532
  global.__bolloonMainTask = { enterMainTask, exitMainTask };
2533
+ // 2026-09-16: 运行失速巡检 (每 60s) — running 但长时间没新进展的运行标 stalled,
2534
+ // 让 UI/CLI 能说"这个运行卡住了", 而不是永远转圈 (crash 由启动时对账兜底)。
2535
+ setInterval(() => {
2536
+ void (async () => {
2537
+ try {
2538
+ const { superviseRuns } = await import('../agents/run-store.js');
2539
+ const r = await superviseRuns();
2540
+ if (r.stalled.length)
2541
+ console.warn(`[runs] 失速巡检: ${r.stalled.length} 条运行标 stalled (${r.stalled.slice(0, 3).join(', ')})`);
2542
+ }
2543
+ catch { /* 巡检失败不影响主流程 */ }
2544
+ })();
2545
+ }, 60_000);
2442
2546
  }
2443
2547
  catch (cronErr) {
2444
2548
  console.warn('[cron] 调度器启动失败 (non-fatal):', cronErr?.message);
@@ -2832,6 +2936,288 @@ ${goalDesc}
2832
2936
  res.status(400).json({ ok: false, severity: 'block', reason: e?.message ?? 'parse error' });
2833
2937
  }
2834
2938
  });
2939
+ // 2026-09-16: 运行记录 (持久化 harness) — 当前 + 历史 agent 运行。跨重载可读。
2940
+ // 2026-09-16 (2-G.1): Skills Manager —— 与 CLI `/skills` `/skill` 读同一份事实 (skills-registry.json + 技能目录)
2941
+ app.get('/api/skills', async (_req, res) => {
2942
+ try {
2943
+ const { getSkillsManager } = await import('../agents/skills-manager.js');
2944
+ const sm = getSkillsManager();
2945
+ const skills = await sm.view();
2946
+ res.json({ count: skills.length, registryPath: (await import('../agents/skills-manager.js')).registryPath(), skills, health: await sm.health() });
2947
+ }
2948
+ catch (err) {
2949
+ res.status(500).json({ error: String(err?.message || err).slice(0, 200) });
2950
+ }
2951
+ });
2952
+ app.get('/api/skills/health', async (_req, res) => {
2953
+ try {
2954
+ const { getSkillsManager } = await import('../agents/skills-manager.js');
2955
+ res.json(await getSkillsManager().health());
2956
+ }
2957
+ catch (err) {
2958
+ res.status(500).json({ error: String(err?.message || err).slice(0, 200) });
2959
+ }
2960
+ });
2961
+ app.get('/api/skills/:name', async (req, res) => {
2962
+ try {
2963
+ const { getSkillsManager } = await import('../agents/skills-manager.js');
2964
+ const s = await getSkillsManager().inspect(String(req.params.name));
2965
+ if (!s) {
2966
+ res.status(404).json({ error: `没有这个技能: ${req.params.name}` });
2967
+ return;
2968
+ }
2969
+ res.json({ skill: s });
2970
+ }
2971
+ catch (err) {
2972
+ res.status(500).json({ error: String(err?.message || err).slice(0, 200) });
2973
+ }
2974
+ });
2975
+ app.post('/api/skills/import', async (req, res) => {
2976
+ try {
2977
+ const ref = String(req.body?.ref || '').trim();
2978
+ if (!ref) {
2979
+ res.status(400).json({ error: '要 body {ref: "bolloon://skill/<cid>" | "ipfs://<cid>" | "<cid>"}' });
2980
+ return;
2981
+ }
2982
+ const { getSkillsManager } = await import('../agents/skills-manager.js');
2983
+ const r = await getSkillsManager().import(ref, { force: !!req.body?.force });
2984
+ if (!r.ok) {
2985
+ res.status(409).json({ error: r.error });
2986
+ return;
2987
+ }
2988
+ res.json({ ok: true, name: r.name, version: r.version, skill: r.skill });
2989
+ }
2990
+ catch (err) {
2991
+ res.status(500).json({ error: String(err?.message || err).slice(0, 200) });
2992
+ }
2993
+ });
2994
+ // enable / disable / approve / validate / quarantine (Express 5 不支持内联正则, 显式写开)
2995
+ for (const action of ['enable', 'disable', 'approve', 'validate', 'quarantine']) {
2996
+ app.post(`/api/skills/:name/${action}`, async (req, res) => {
2997
+ try {
2998
+ const { getSkillsManager } = await import('../agents/skills-manager.js');
2999
+ const sm = getSkillsManager();
3000
+ const name = String(req.params.name);
3001
+ const r = action === 'approve' ? await sm.approve(name, 'web')
3002
+ : action === 'quarantine' ? await sm.quarantine(name, String(req.body?.reason || 'web 手动隔离'))
3003
+ : action === 'enable' ? await sm.enable(name)
3004
+ : action === 'disable' ? await sm.disable(name)
3005
+ : await sm.validate(name);
3006
+ if (!r.ok && !r.skill) {
3007
+ res.status(409).json({ error: r.reason || r.issues || '操作未完成' });
3008
+ return;
3009
+ }
3010
+ res.json({ ok: r.ok, name, action, skill: r.skill, issues: r.issues });
3011
+ }
3012
+ catch (err) {
3013
+ res.status(500).json({ error: String(err?.message || err).slice(0, 200) });
3014
+ }
3015
+ });
3016
+ }
3017
+ // 2026-09-16 (M2-B): 长期执行层诊断 + 手动推进 —— 与 CLI /supervise 读同一份持久化记录
3018
+ app.get('/api/supervisor', async (_req, res) => {
3019
+ try {
3020
+ const { getSupervisor } = await import('../agents/execution-supervisor.js');
3021
+ const { readSupervisorState, supervisorStatePath } = await import('../agents/supervisor-host.js');
3022
+ const { wakeReport, listRunnableGoals } = await import('../agents/goal-store.js');
3023
+ const { runnable, skipped } = await listRunnableGoals({ now: Date.now() });
3024
+ res.json({
3025
+ supervisor: getSupervisor().status(),
3026
+ /** 宿主身份/心跳 (谁在跑、跑到哪、上次有没有好好停) */
3027
+ host: await readSupervisorState(),
3028
+ hostStatePath: supervisorStatePath(),
3029
+ wake: await wakeReport(),
3030
+ runnable: runnable.map((g) => ({ goalId: g.goalId, objective: g.objective, status: g.status })),
3031
+ skipped,
3032
+ });
3033
+ }
3034
+ catch (err) {
3035
+ res.status(500).json({ error: String(err?.message || err).slice(0, 200) });
3036
+ }
3037
+ });
3038
+ // 手动踢一个调度周期 (调试/验收用; 不改变任何"事实来源")
3039
+ app.post('/api/supervisor/tick', async (_req, res) => {
3040
+ try {
3041
+ const { getSupervisor } = await import('../agents/execution-supervisor.js');
3042
+ const report = await getSupervisor().tickOnce();
3043
+ res.json({ ok: true, report });
3044
+ }
3045
+ catch (err) {
3046
+ res.status(500).json({ error: String(err?.message || err).slice(0, 200) });
3047
+ }
3048
+ });
3049
+ // 2026-09-16 (2-E 第 4 类): 外部事件到达 → 唤醒在等它的 Goal (不重发已成功的请求)
3050
+ app.post('/api/goals/:goalId/wake', async (req, res) => {
3051
+ const goalId = String(req.params.goalId);
3052
+ try {
3053
+ const { getSupervisor } = await import('../agents/execution-supervisor.js');
3054
+ const { readGoal, setContinuation } = await import('../agents/goal-store.js');
3055
+ const g = await readGoal(goalId);
3056
+ if (!g) {
3057
+ res.status(404).json({ error: `goal 不存在: ${goalId}` });
3058
+ return;
3059
+ }
3060
+ const woke = await getSupervisor().notifyExternal(goalId);
3061
+ if (!woke) {
3062
+ // 没在等外部事件也要如实回答 (可能它其实在等人/在跑), 但允许显式加急
3063
+ if (req.body?.force) {
3064
+ await setContinuation(goalId, { wakeReason: 'active', autoContinue: true, wakeAt: undefined, needsExternal: undefined });
3065
+ res.json({ ok: true, goalId, woke: false, forced: true, note: `原状态 ${g.status} (不在等外部事件), 已按 force 加急` });
3066
+ return;
3067
+ }
3068
+ res.status(409).json({ error: `Goal 不在等外部事件 (status=${g.status}, wakeReason=${g.continuation?.wakeReason || '无'})`, force_hint: '加 body {"force":true} 可强制加急' });
3069
+ return;
3070
+ }
3071
+ res.json({ ok: true, goalId, woke: true, note: '已唤醒: 下一次 Supervisor tick 会推进它' });
3072
+ }
3073
+ catch (err) {
3074
+ res.status(500).json({ error: String(err?.message || err).slice(0, 200) });
3075
+ }
3076
+ });
3077
+ // 2026-09-16 (M5): 运行列表 —— Web 与 CLI 读同一份 run 事实 (~/.bolloon/runs)
3078
+ app.get('/api/runs', async (req, res) => {
3079
+ try {
3080
+ const { listRuns, formatRunLine } = await import('../agents/run-store.js');
3081
+ const status = String(req.query?.status || '').trim();
3082
+ const limit = Number(req.query?.limit || 20);
3083
+ const runs = await listRuns({ status: status ? status : undefined, limit });
3084
+ res.json({
3085
+ count: runs.length,
3086
+ runs,
3087
+ lines: runs.map(formatRunLine),
3088
+ });
3089
+ }
3090
+ catch (err) {
3091
+ res.status(500).json({ error: String(err?.message || err).slice(0, 200) });
3092
+ }
3093
+ });
3094
+ // 2026-09-16 (M5): 单个 run + 停止原因/checkpoint/最近步骤/recovery/harness 事件 (两端同一份事实)
3095
+ app.get('/api/runs/:runId', async (req, res) => {
3096
+ try {
3097
+ const { readRun } = await import('../agents/run-store.js');
3098
+ const run = await readRun(String(req.params.runId));
3099
+ if (!run) {
3100
+ res.status(404).json({ error: `run 不存在: ${req.params.runId}` });
3101
+ return;
3102
+ }
3103
+ const goal = run.goalId ? await (await import('../agents/goal-store.js')).readGoal(run.goalId) : null;
3104
+ res.json({ run, goal, checkpoint: run.checkpoint, steps: run.steps, recovery: run.recovery, harness: run.harness || [] });
3105
+ }
3106
+ catch (err) {
3107
+ res.status(500).json({ error: String(err?.message || err).slice(0, 200) });
3108
+ }
3109
+ });
3110
+ // 2026-09-16 (M5): 继续 interrupted/stalled/paused/needs_human/awaiting_external 的运行 —— 真从 checkpoint 继续
3111
+ app.post('/api/runs/:runId/resume', async (req, res) => {
3112
+ const runId = String(req.params.runId);
3113
+ try {
3114
+ const { readRun, RESUMABLE_STATUSES } = await import('../agents/run-store.js');
3115
+ const run = await readRun(runId);
3116
+ if (!run) {
3117
+ res.status(404).json({ error: `run 不存在: ${runId}` });
3118
+ return;
3119
+ }
3120
+ // 先做只读校验: 不可恢复的状态明确拒绝 (不假装"已开始"), 避免前端以为在跑
3121
+ if (!RESUMABLE_STATUSES.includes(run.status)) {
3122
+ res.status(409).json({ error: `状态 ${run.status} 不可恢复 (可恢复: ${RESUMABLE_STATUSES.join('/')})` });
3123
+ return;
3124
+ }
3125
+ if (!run.channelId) {
3126
+ res.status(400).json({ error: '该 run 没有 channelId, 无法在 web 端恢复 (请在 CLI 用 /resume)' });
3127
+ return;
3128
+ }
3129
+ const agent = await getAgentForChannel(run.channelId);
3130
+ if (!agent?.resumeRun) {
3131
+ res.status(409).json({ error: '该 channel 的 agent 不支持 resumeRun' });
3132
+ return;
3133
+ }
3134
+ // 异步执行: resume 可能跑几分钟; 状态从 /api/runs/:runId 读 (recovering → running → done/failed)
3135
+ void agent.resumeRun(runId).then((r) => {
3136
+ if (!r?.ok)
3137
+ console.warn(`[runs] resume ${runId} 失败:`, r?.reason);
3138
+ }).catch((err) => console.warn(`[runs] resume ${runId} 异常:`, err?.message));
3139
+ res.json({ ok: true, accepted: true, runId, note: '已开始恢复; 状态请读 GET /api/runs/:runId' });
3140
+ }
3141
+ catch (err) {
3142
+ res.status(500).json({ error: String(err?.message || err).slice(0, 200) });
3143
+ }
3144
+ });
3145
+ // 2026-09-16 (M5): 人工批准 —— needs_human 的运行经人确认后继续 (= 带批准的 resume)
3146
+ app.post('/api/runs/:runId/approve', async (req, res) => {
3147
+ const runId = String(req.params.runId);
3148
+ try {
3149
+ const { readRun, recordRecovery } = await import('../agents/run-store.js');
3150
+ const run = await readRun(runId);
3151
+ if (!run) {
3152
+ res.status(404).json({ error: `run 不存在: ${runId}` });
3153
+ return;
3154
+ }
3155
+ if (run.status !== 'needs_human') {
3156
+ res.status(409).json({ error: `只有 needs_human 的运行需要批准 (当前 ${run.status})` });
3157
+ return;
3158
+ }
3159
+ await recordRecovery(runId, { errorClass: run.errorClass || 'unknown', message: '人工批准后继续', action: 'resume' });
3160
+ if (!run.channelId) {
3161
+ res.status(400).json({ error: '该 run 没有 channelId, 请在 CLI 用 /approve' });
3162
+ return;
3163
+ }
3164
+ const agent = await getAgentForChannel(run.channelId, run.channelId, run.agentId, {});
3165
+ if (!agent?.resumeRun) {
3166
+ res.status(409).json({ error: '该 channel 的 agent 不支持 resumeRun' });
3167
+ return;
3168
+ }
3169
+ void agent.resumeRun(runId).catch((err) => console.warn(`[runs] approve ${runId} 异常:`, err?.message));
3170
+ res.json({ ok: true, accepted: true, runId, approved: true });
3171
+ }
3172
+ catch (err) {
3173
+ res.status(500).json({ error: String(err?.message || err).slice(0, 200) });
3174
+ }
3175
+ });
3176
+ // 2026-09-16 (M5): 暂停 / 中止 —— 只改 run 状态; 运行中的 agent 会在下一次循环检查时如实停下
3177
+ // (Express 5 的 path-to-regexp 不再支持 `:action(a|b)` 内联正则, 所以两条写开)
3178
+ for (const [suffix, to] of [['pause', 'paused'], ['abort', 'aborted']]) {
3179
+ app.post(`/api/runs/:runId/${suffix}`, async (req, res) => {
3180
+ const runId = String(req.params.runId);
3181
+ try {
3182
+ const { setRunStatus } = await import('../agents/run-store.js');
3183
+ const r = await setRunStatus(runId, to, { error: `外部请求 (${suffix})` });
3184
+ if (!r.ok) {
3185
+ res.status(409).json({ error: r.reason || '状态迁移被拒绝' });
3186
+ return;
3187
+ }
3188
+ res.json({ ok: true, runId, status: to, note: '运行中的 agent 会在下一轮循环检查时停下 (轮内不打断)' });
3189
+ }
3190
+ catch (err) {
3191
+ res.status(500).json({ error: String(err?.message || err).slice(0, 200) });
3192
+ }
3193
+ });
3194
+ }
3195
+ // 2026-09-16 (M2): 目标 (Goal) —— 目标事实来源; runId → goalId → objective/successCriteria 反查链
3196
+ app.get('/api/goals', async (req, res) => {
3197
+ try {
3198
+ const { listGoals, formatGoalLine } = await import('../agents/goal-store.js');
3199
+ const status = String(req.query?.status || '').trim();
3200
+ const goals = await listGoals({ status: status ? status : undefined, limit: Number(req.query?.limit || 30) });
3201
+ res.json({ count: goals.length, goals, lines: goals.map(formatGoalLine) });
3202
+ }
3203
+ catch (err) {
3204
+ res.status(500).json({ error: String(err?.message || err).slice(0, 200) });
3205
+ }
3206
+ });
3207
+ app.get('/api/goals/:goalId', async (req, res) => {
3208
+ try {
3209
+ const { readGoal, evaluateGoalCompletion } = await import('../agents/goal-store.js');
3210
+ const goal = await readGoal(String(req.params.goalId));
3211
+ if (!goal) {
3212
+ res.status(404).json({ error: `goal 不存在: ${req.params.goalId}` });
3213
+ return;
3214
+ }
3215
+ res.json({ goal, completion: evaluateGoalCompletion(goal) });
3216
+ }
3217
+ catch (err) {
3218
+ res.status(500).json({ error: String(err?.message || err).slice(0, 200) });
3219
+ }
3220
+ });
2835
3221
  // 2026-08-13 (Phase E1): Agent 服务 Registry — 发现层 (OrbitDB 去中心化 + 本地 fallback)
2836
3222
  app.get('/api/registry', async (_req, res) => {
2837
3223
  try {
@@ -3020,14 +3406,61 @@ ${goalDesc}
3020
3406
  // 2026-09-15: agent-delegate (manifest 协议 + agent_delegate) **启动即挂载**
3021
3407
  // 文档 bolloon-gateway-join.md 第 3 节要求 POST /api/agent/register 可用;
3022
3408
  // 之前只在 iroh 懒初始化 (/api/iroh/info 首次访问) 的收尾挂上 → 没触发时恒 404。
3409
+ //
3410
+ // 2026-09-15 (二修): 同时注入**真执行器** —— 被委派的一方不再是
3411
+ // `resultCid: mock-<ts>` 的假签收: 真的跑本地 agent session 出结果,
3412
+ // 并把结果存进 OrbitDB 得到真实内容寻址 CID; 无活跃 channel / 无 LLM 时如实报错。
3413
+ const delegateExecutor = async (req) => {
3414
+ try {
3415
+ const channels = await loadChannels();
3416
+ const active = (() => {
3417
+ try {
3418
+ const raw = fsSync.existsSync(path.join(process.env.HOME || '/tmp', '.bolloon', 'active-channel.json'))
3419
+ ? fsSync.readFileSync(path.join(process.env.HOME || '/tmp', '.bolloon', 'active-channel.json'), 'utf-8') : '';
3420
+ return raw ? JSON.parse(raw) : null;
3421
+ }
3422
+ catch {
3423
+ return null;
3424
+ }
3425
+ })();
3426
+ const ch = channels.find((c) => c.id === active?.channelId) || channels.find((c) => c.id === active?.id) || channels[0];
3427
+ if (!ch)
3428
+ return { ok: false, summary: 'no local channel to execute with', error: 'no-channel' };
3429
+ const agent = await getAgentForChannel(ch.id, ch.publicKey, ch.name, ch.didDocument);
3430
+ const task = [
3431
+ `【被委派任务 · 能力 ${req.capability}】`,
3432
+ req.instruction,
3433
+ req.docPath ? `\n资料路径: ${req.docPath}` : '',
3434
+ req.docContent ? `\n资料内容:\n${req.docContent}` : '',
3435
+ `\n(来自 agent ${req.fromAgentId || 'unknown'}),请直接给出可交付结果。`,
3436
+ ].join('');
3437
+ const out = await agent.prompt(task);
3438
+ const text = String(out || '').trim();
3439
+ if (!text || text.startsWith('❌') || text.startsWith('[AI 服务调用失败]')) {
3440
+ return { ok: false, summary: text.slice(0, 800) || '(空结果)', error: 'execution-failed' };
3441
+ }
3442
+ // 真结果 → 内容寻址存储 (CID), 失败不编造 CID, 只回摘要
3443
+ let cid;
3444
+ try {
3445
+ const { getCIDDatabase } = await import('../orbitdb/cid-database.js');
3446
+ const rec = await getCIDDatabase().save({ agentId: req.targetAgentId, type: 'context', content: text, metadata: { capability: req.capability, fromAgentId: req.fromAgentId, kind: 'delegate-result' } });
3447
+ cid = rec?.id;
3448
+ }
3449
+ catch { /* 存不上就不给 CID */ }
3450
+ return { ok: true, summary: text.slice(0, 4000), resultCid: cid };
3451
+ }
3452
+ catch (e) {
3453
+ return { ok: false, summary: `delegate executor failed: ${String(e?.message || e).slice(0, 300)}`, error: 'executor-error' };
3454
+ }
3455
+ };
3023
3456
  let agentDelegateMounted = false;
3024
3457
  try {
3025
3458
  const delegateTransport = createIrohDelegateTransport({ verbose: true });
3026
3459
  // 注意: createAgentDelegateApp 内部声明的是绝对路径 (/api/agent/...),
3027
3460
  // 所以挂载时**不能**再带 '/api/agent' 前缀 (否则变成 /api/agent/api/agent/... 恒 404)
3028
- app.use(createAgentDelegateApp(delegateTransport));
3461
+ app.use(createAgentDelegateApp(delegateTransport, { execute: delegateExecutor }));
3029
3462
  agentDelegateMounted = true;
3030
- console.log('[agent-delegate] 已挂载到 /api/agent (启动即用: local-manifest / register / pick / delegate)');
3463
+ console.log('[agent-delegate] 已挂载到 /api/agent (启动即用: local-manifest / register / pick / delegate, 真执行器已注入)');
3031
3464
  }
3032
3465
  catch (e) {
3033
3466
  console.warn('[agent-delegate] 挂载失败 (非致命):', e?.message);
@@ -6588,10 +7021,10 @@ ${goalDesc}
6588
7021
  if (!agentDelegateMounted) {
6589
7022
  try {
6590
7023
  const delegateTransport = createIrohDelegateTransport({ verbose: true });
6591
- const delegateApp = createAgentDelegateApp(delegateTransport);
7024
+ const delegateApp = createAgentDelegateApp(delegateTransport, { execute: delegateExecutor });
6592
7025
  app.use(delegateApp);
6593
7026
  agentDelegateMounted = true;
6594
- console.log('[iroh API] agent-delegate app 已挂载到 /api/agent (补挂)');
7027
+ console.log('[iroh API] agent-delegate app 已挂载到 /api/agent (补挂, 真执行器已注入)');
6595
7028
  }
6596
7029
  catch (e) {
6597
7030
  console.error('[iroh API] 挂载 agent-delegate app 失败:', e);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bolloon/bolloon-agent",
3
- "version": "0.4.23",
3
+ "version": "0.4.25",
4
4
  "type": "module",
5
5
  "description": "P2P AI Document Agent - 全局安装后执行 `bolloon` 启动产品",
6
6
  "main": "dist/cli-entry.js",
@@ -130,7 +130,7 @@
130
130
  "vitest": "^5.0.0"
131
131
  },
132
132
  "build": {
133
- "appId": "com.bolloon.agent",
133
+ "appId": "com.hibs.bolloon",
134
134
  "productName": "Bolloon Agent",
135
135
  "directories": {
136
136
  "output": "release",