Claude Cursor Skill

cordis-plugin-sofagent

一次挂载 sofagent 全套能力——6 项能力一次到位(注入 · 审计与验收 · 经验 · 回溯 · 巡检 · FDE 三域)(seam: non-seam:plugin-suite)——只编排不重实现——sofagent 约束层在 DSH(DeepSeek Harness)生态的插件形态。

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download KongFangXun-sofagent-engine_dsh-plugins_cordis-plugin-sofagent-b8c9032.zip · 12 KB
Part of kongfangxun/sofagent — 15 skills

Install

skills CLI npx skills add https://github.com/KongFangXun/sofagent/tree/main/engine/dsh-plugins/cordis-plugin-sofagent
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install kongfangxun-sofagent@llmmart
Git git clone https://github.com/KongFangXun/sofagent.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole kongfangxun/sofagent collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

cordis-plugin-sofagent

一次挂载 sofagent 全套能力——6 项能力一次到位(注入 · 审计与验收 · 经验 · 回溯 · 巡检 · FDE 三域)(seam: non-seam:plugin-suite)——只编排不重实现,能力仍由各原子插件提供

用途

一次 apply 把 6 个原子插件(inject / audit / evolve / rollback / daemon / fde)逐个挂到同一个 ctx 上,挂完把结果(loaded / failed / total)注册成 sofagent.suite 服务。适合「整套装上、不想逐个装」的场景。什么时候用:一次拿到全套约束层能力、不想逐个挑插件时。

三条硬约束:

  1. 只编排,不重实现 —— 每个能力仍由原子插件 provide,本层只依次调用它们的 apply,不复制任何子插件逻辑;
  2. 逐个降级,不整挂失败 —— 缺任一个原子插件只记入 failed 数组(含包名与原因),其余 5 个照常加载;
  3. 不替代细粒度插件 —— 6 个原子插件全部保留,本插件是新增的整装选项,按需二选一。

本插件随 sofagent 主线版本发布,走宿主挂载通道:skillhub install cordis-plugin-sofagent。⚠️ 本插件不在 npm 通道——11 款插件(7 DSH + 4 OpenClaw,v1.4.9 P2 合并批 14→11)均为 private,npm 装不到;npm 侧另有库通道 npm i -g sofagent(总包 engine/umbrella,聚合 @sofagent/audit / mcp / orchestrator / daemon 四个能力包,不含本插件)。两条通道按所在环境二选一,互不依赖、互不为前置。版本号与 sofagent 主线对齐。

本插件是宿主挂载入口;npm 安装入口见裸名总包(engine/umbrella,包名 sofagent)—— 两个入口互不引用为前置,按使用场景各取其一。

相关链接

  • sofagent 主仓:https://github.com/KongFangXun/sofagent
  • 开发日志:docs/changelog/v1.4/v1.4.0.md(DSH 插件家族)
Files (sofagent)
  • src
    • index.test.ts 12.7 KB
      // ============================================================
      // cordis-plugin-sofagent · 插件单测(v1.4.8 第8批 · 聚合编排层;v1.4.9 P2 合并批:9→6)
      // ============================================================
      // 断言面 = 三条硬约束(P2 合并后口径):
      //   ① 只编排不重实现 —— 注册的服务只有 6 个原子插件 + 本层的 sofagent.suite
      //   ② 逐个降级不整挂失败 —— 缺一个原子插件,其余 5 个照常;failed 精确报出缺的那一个
      //   ③ 不替代细粒度插件 —— 编排清单与 plugins.json 的 suite 段逐条一致(6 个原子插件全在)
      // ============================================================
      
      import { describe, it, expect, vi } from 'vitest';
      import path from 'node:path';
      import fs from 'node:fs';
      import { fileURLToPath } from 'node:url';
      import plugin, { pluginMeta, capability, suite } from './index';
      
      const HERE = path.dirname(fileURLToPath(import.meta.url));
      const PLUGIN_DIR = path.resolve(HERE, '..');
      const SIBLINGS_DIR = path.resolve(PLUGIN_DIR, '..');
      
      /** 聚合层 apply 的结果快照(与 src/index.ts 的 SuiteReport 同形) */
      interface SuiteReport {
        loaded: string[];
        failed: Array<{ name: string; reason: string }>;
        total: number;
        capability: string;
        viaHost: string[];
        viaDirect: string[];
        unloadHook: 'ctx.on' | 'apply-return';
      }
      
      /** 假 ctx 的可选宿主能力开关(用于覆盖三条挂载/卸载路径) */
      interface CtxOptions {
        /** 是否提供宿主惯用法 ctx.plugin(false = 裸 ctx,只能回落直呼 apply) */
        withHostPlugin?: boolean;
        /** 是否提供宿主生命周期钩子 ctx.on('dispose', …) */
        withDisposeHook?: boolean;
        /** ctx.plugin 是否抛错(模拟宿主拒收 → 必须回落直呼) */
        rejectHostMount?: boolean;
      }
      
      /**
       * 构造鸭子类型 ctx:**对齐 cordis 4.x 的真实契约**(实测 ~/.dsh/.../@deepseek-ai/cordis 4.0.2)。
       *   ① `provide(name, value)` **返回 disposer**(宿主实现走 fiber.effect,返回反注册函数);
       *   ② `plugin(p)` 挂载插件并返回 ForkScope(含 `dispose`),句柄 dispose 时反向撤销该子插件的注册;
       *   ③ `on('dispose', fn)` 登记卸载监听。
       * 🔴 刻意不 import 任何宿主类型——与适配层红线同口径(ctx: unknown + 结构访问)。
       */
      function makeCtx(opts: CtxOptions = {}) {
        const { withHostPlugin = true, withDisposeHook = true, rejectHostMount = false } = opts;
        const services = new Map<string, unknown>();
        /** ctx.on('dispose') 登记的回调(模拟宿主卸载事件触发面) */
        const onDispose: Array<() => unknown> = [];
        /** 经宿主路径挂载过的插件(断言「走的是宿主惯用法」) */
        const mounted: unknown[] = [];
      
        const ctx: Record<string, unknown> = {
          services,
          onDispose,
          mounted,
          provide: (name: string, service: unknown): (() => void) => {
            services.set(name, service);
            return () => {
              services.delete(name);
            };
          },
          sofagent: {},
        };
      
        if (withHostPlugin) {
          ctx.plugin = (p: { apply?: (c: unknown) => unknown }): { dispose: () => Promise<void> } => {
            if (rejectHostMount) throw new Error('宿主拒收(合成:契约不符)');
            mounted.push(p);
            // 宿主职责:调用插件 apply 并**按 fiber 托管其返回的 disposer**
            const childDisposers: Array<() => unknown> = [];
            const ret = typeof p?.apply === 'function' ? p.apply(ctx) : undefined;
            if (typeof ret === 'function') childDisposers.push(ret as () => unknown);
            return {
              dispose: async (): Promise<void> => {
                for (const undo of childDisposers.splice(0).reverse()) await undo();
              },
            };
          };
        }
        if (withDisposeHook) {
          ctx.on = (event: string, listener: () => unknown): (() => void) => {
            if (event === 'dispose') onDispose.push(listener);
            return () => {
              const i = onDispose.indexOf(listener);
              if (i >= 0) onDispose.splice(i, 1);
            };
          };
        }
        return ctx as {
          services: Map<string, unknown>;
          onDispose: Array<() => unknown>;
          mounted: unknown[];
          sofagent: Record<string, unknown>;
          plugin?: unknown;
          on?: unknown;
          provide: (name: string, service: unknown) => () => void;
        };
      }
      
      /** 取聚合层自己 provide 的报告 */
      function reportOf(ctx: ReturnType<typeof makeCtx>): SuiteReport {
        return ctx.services.get('sofagent.suite') as SuiteReport;
      }
      
      describe('cordis-plugin-sofagent', () => {
        it('插件元数据完整(id/version/seam/description 与 package.json SSOT 对齐)', () => {
          const pkg = require('../package.json') as { version: string };
          expect(pluginMeta.id).toBe('cordis-plugin-sofagent');
          expect(pluginMeta.version).toBe(pkg.version); // SSOT 对齐:版本跟随主版本(读 package.json)
          expect(pluginMeta.seam).toBe('non-seam:plugin-suite');
          expect(pluginMeta.description).toContain(`seam: ${pluginMeta.seam}`); // 描述不滞后于契约
        });
      
        it('编排清单恰好 6 条(v1.4.9 P2 合并批),且与 plugins.json 的 suite 段逐条一致', () => {
          const manifest = require('../../plugins.json') as {
            plugins: Array<{ id: string; kind?: string; capability: string; suite?: string[] }>;
          };
          const entry = manifest.plugins.find((p) => p.id === pluginMeta.id);
          expect(entry, 'plugins.json 未登记聚合条目').toBeDefined();
          expect(entry!.kind).toBe('suite'); // 聚合型条目(bridgePkg/bridgeApi 语义不适用)
          expect(capability).toBe(entry!.capability);
          expect(suite).toHaveLength(6);
          expect(suite.map(([, pkg]) => pkg).sort()).toEqual([...entry!.suite!].sort());
        });
      
        it('P2 合并落位:SUITE 不含已并入的 -gate/-ontology/-commons 三包', () => {
          const pkgs = suite.map(([, pkg]) => pkg);
          expect(pkgs).not.toContain('cordis-plugin-sofagent-gate');
          expect(pkgs).not.toContain('cordis-plugin-sofagent-ontology');
          expect(pkgs).not.toContain('cordis-plugin-sofagent-commons');
        });
      
        it('一次 apply 挂满 6 个能力,且 6 个 sofagent.* 服务逐个可访问', async () => {
          const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
          const ctx = makeCtx();
          await plugin.apply(ctx);
          const report = reportOf(ctx);
          expect(report.total).toBe(6);
          expect(report.failed).toEqual([]);
          expect([...report.loaded].sort()).toEqual(suite.map(([key]) => key).sort());
          // 逐个断言(不只看数组长度)——每个原子插件的能力确实挂上了
          for (const [key] of suite) {
            const svc = ctx.services.get(`sofagent.${key}`) as { invoke?: unknown } | undefined;
            expect(svc, `sofagent.${key} 未注册`).toBeDefined();
            expect(typeof svc!.invoke).toBe('function');
          }
          errSpy.mockRestore();
        });
      
        it('只编排不重实现:注册面 = 6 个原子服务 + sofagent.suite,本层不复制任何子插件逻辑', async () => {
          const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
          const ctx = makeCtx();
          await plugin.apply(ctx);
          const names = [...ctx.services.keys()].sort();
          expect(names).toHaveLength(7); // 6 原子 + 1 聚合报告
          expect(names.filter((n) => n !== 'sofagent.suite')).toHaveLength(6);
          expect(reportOf(ctx).capability).toBe(capability);
          errSpy.mockRestore();
        });
      
        it('逐个降级:某原子插件缺 dist/ → 其余 5 个照常加载,failed 精确报出缺的那一个', async () => {
          const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
          const fdeDist = path.join(SIBLINGS_DIR, 'cordis-plugin-sofagent-fde', 'dist', 'index.js');
          const stash = `${fdeDist}.__hidden__`;
          expect(fs.existsSync(fdeDist), '前置条件:fde 已 build(npm run build)').toBe(true);
          fs.renameSync(fdeDist, stash);
          try {
            // 清模块缓存后重新 import——否则拿到的是前面测试已缓存的 fde 模块,注入不生效
            vi.resetModules();
            const fresh = (await import('./index')).default;
            const ctx = makeCtx();
            await fresh.apply(ctx);
            const report = reportOf(ctx);
            expect(report.loaded).toHaveLength(5);
            expect(report.loaded).not.toContain('fde');
            expect(report.failed).toHaveLength(1); // 精确 1 个
            expect(report.failed[0].name).toBe('fde');
            expect(report.failed[0].reason.length).toBeGreaterThan(0);
            expect(ctx.services.get('sofagent.fde')).toBeUndefined();
            // 其余 5 个能力仍逐个可用(缺一个不让其余 5 个挂掉)
            for (const [key] of suite.filter(([k]) => k !== 'fde')) {
              expect(ctx.services.get(`sofagent.${key}`), `sofagent.${key} 应仍可用`).toBeDefined();
            }
          } finally {
            if (fs.existsSync(stash)) fs.renameSync(stash, fdeDist);
            errSpy.mockRestore();
          }
        });
      
        it('A2·挂载路径:ctx.plugin 在场时走宿主惯用法(逐插件带 inject 声明),不静默直呼', async () => {
          const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
          const ctx = makeCtx();
          await plugin.apply(ctx);
          const report = reportOf(ctx);
          expect(report.viaHost, '宿主路径挂载数').toHaveLength(6);
          expect(report.viaDirect, '宿主可用时回落路径应为空').toEqual([]);
          expect(ctx.mounted, '宿主 ctx.plugin 收到的插件数').toHaveLength(6);
          expect(report.loaded.length + report.failed.length, '守恒:loaded + failed = total').toBe(report.total);
          // 走宿主路径的插件必须自带 inject 声明——否则宿主无从做就绪门控(A2 根因 1)
          for (const p of ctx.mounted as Array<{ inject?: readonly string[] }>) {
            expect(Array.isArray(p.inject), '插件对象须声明 inject').toBe(true);
            expect(p.inject).toContain('settings');
            expect(p.inject).toContain('dynamicCordisRunner');
          }
          errSpy.mockRestore();
        });
      
        it('A2·卸载路径:聚合层卸载后本层与 6 个原子服务全部反注册(不留残留)', async () => {
          const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
          const ctx = makeCtx();
          const dispose = await plugin.apply(ctx);
          expect(ctx.services.get('sofagent.suite'), '装载后本层报告服务在').toBeDefined();
          expect(ctx.services.get('sofagent.audit'), '装载后原子服务在').toBeDefined();
          expect(ctx.services.size, '装载面 = 6 原子 + 1 聚合报告').toBe(7);
          // 宿主生命周期钩子已登记(双保险之一)
          expect(ctx.onDispose).toHaveLength(1);
          expect(reportOf(ctx).unloadHook).toBe('ctx.on');
          // 触发卸载(模拟宿主 dispose 事件)
          await ctx.onDispose[0]();
          expect(ctx.services.get('sofagent.suite'), '卸载后本层服务须反注册').toBeUndefined();
          expect(ctx.services.get('sofagent.audit'), '卸载后原子服务须反注册').toBeUndefined();
          expect(ctx.services.size, '卸载后 ctx 上不留任何 sofagent.* 服务').toBe(0);
          // 幂等:apply 返回值再调一次不抛、不复活
          await dispose();
          expect(ctx.services.size).toBe(0);
          errSpy.mockRestore();
        });
      
        it('A2·回落路径:宿主拒收 ctx.plugin 时回落直呼 apply,逐个降级不整挂失败', async () => {
          const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
          const ctx = makeCtx({ rejectHostMount: true });
          await plugin.apply(ctx);
          const report = reportOf(ctx);
          expect(report.viaHost, '宿主拒收 → 不应有宿主路径挂载').toEqual([]);
          expect(report.viaDirect, '全部走回落直呼').toHaveLength(6);
          expect(report.loaded).toHaveLength(6);
          expect(report.failed).toEqual([]);
          expect(report.loaded.length + report.failed.length).toBe(report.total);
          errSpy.mockRestore();
        });
      
        it('A2·静默空转已修:子插件无 apply 时计入 failed,不得谎报 loaded', async () => {
          const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
          vi.doMock('cordis-plugin-sofagent-evolve', () => ({ default: { pluginMeta: {}, capability: '' } })); // 无 apply
          vi.resetModules();
          try {
            const fresh = (await import('./index')).default;
            const ctx = makeCtx({ withHostPlugin: false, withDisposeHook: false }); // 裸 ctx:必走回落分支
            await fresh.apply(ctx);
            const report = reportOf(ctx);
            expect(report.loaded, '无 apply 者不得计入 loaded').not.toContain('evolve');
            expect(report.failed.map((f) => f.name)).toContain('evolve');
            expect(report.loaded).toHaveLength(5);
            expect(report.loaded.length + report.failed.length, '守恒:6 = loaded + failed').toBe(6);
            expect(ctx.services.get('sofagent.evolve'), '缺 apply 者不得注册服务').toBeUndefined();
            expect(report.unloadHook, '裸 ctx 无 ctx.on → 仅靠 apply 返回值').toBe('apply-return');
          } finally {
            vi.doUnmock('cordis-plugin-sofagent-evolve');
            vi.resetModules();
            errSpy.mockRestore();
          }
        });
      });
      
    • index.ts 10.8 KB
      // cordis-plugin-sofagent · DSH 反向插件(v1.5.2 第8批 · 聚合编排层;v1.5.2 P2 合并批:SUITE 9→6)
      // seam 挂载:non-seam:plugin-suite    # 语义:非宿主事件接入(插件聚合)——一次 apply 逐个挂载 6 个原子插件;能力仍由各原子插件 provide
      // 清单生成源 = engine/dsh-plugins/plugins.json(生成 package.json 的 description/sofagent/dsh/optionalDependencies 段与 cordis.patch.yml);本文件的 seam 字面量由生成器 --check 与之对账。
      //
      // 🔴 三条硬约束(缺一不可):
      //   ① 只编排,不重实现——每个能力仍由原子插件 provide,本层只依次调用它们的 apply;
      //   ② 逐个降级,不整挂失败——缺任一原子插件只记入 failed 数组,其余 5 个照常加载;
      //   ③ 不替代细粒度插件——6 个原子插件全部保留,本插件是**新增的整装选项**,不是替代品。
      //
      // v1.5.2 P2 合并说明:原 -ontology/-commons 并入 -fde(厚插件三域),
      // 原 -gate 并入 -audit(验收 seam 四值)——原子插件 9→6,本 SUITE 同批收口。
      
      const SUITE: ReadonlyArray<readonly [string, string]> = [
        ['inject', 'cordis-plugin-sofagent-inject'],
        ['audit', 'cordis-plugin-sofagent-audit'],
        ['evolve', 'cordis-plugin-sofagent-evolve'],
        ['rollback', 'cordis-plugin-sofagent-rollback'],
        ['daemon', 'cordis-plugin-sofagent-daemon'],
        ['fde', 'cordis-plugin-sofagent-fde'],
      ];
      
      /** 插件自己的 package.json(版本 SSOT 在插件自身,与其余 6 个插件同形) */
      const HOST_PKG = require('../package.json') as { version?: string };
      
      /**
       * 插件元数据(DSH profile / 注册表消费)。
       * 纯声明——本层**不注册任何自己的能力**,故此处只有身份与 seam,没有 bridge 字段。
       */
      export const pluginMeta = {
        id: 'cordis-plugin-sofagent',
        version: HOST_PKG.version ?? '0.0.0-unknown',
        description: '一次挂载 sofagent 全套能力(聚合编排层,只编排不重实现)(seam: non-seam:plugin-suite)',
        seam: 'non-seam:plugin-suite',
      } as const;
      
      /** 依赖的 sofagent 能力说明(DSH skill 引导链展示) */
      export const capability = '一次挂载 sofagent 全套能力(6 项)';
      
      /** 被编排的原子插件清单(短名 → 包名)——供外部只读查阅(与 plugins.json 的 suite 段同源) */
      export const suite: ReadonlyArray<readonly [string, string]> = SUITE;
      
      /**
       * 一次 apply 的结果快照:哪几个挂上了、哪几个没挂上(失败**逐个可见**,不静默)。
       * 另记「走的哪条挂载路径」与「卸载钩子形态」——三条信息合起来才看得出是否有静默降级。
       */
      export interface SuiteReport {
        /** 成功挂载的原子插件短名,如 ['inject', 'audit', …](宿主路径=宿主已收;回落路径=apply 已执行) */
        readonly loaded: string[];
        /** 未挂上的原子插件(短名 + 失败原因)——非空即说明该能力缺席,但其余能力不受影响 */
        readonly failed: Array<{ name: string; reason: string }>;
        /** 原子插件总数(恒为 6) */
        readonly total: number;
        /** 能力说明(与 capability 同值) */
        readonly capability: string;
        /** 经宿主惯用法 `ctx.plugin()` 挂载的短名——这些拿到 inject 就绪门控 + 宿主托管卸载 */
        readonly viaHost: string[];
        /** 经回落路径「直呼 apply」挂载的短名——裸 ctx / 宿主无 ctx.plugin 时即全部 6 个 */
        readonly viaDirect: string[];
        /** 卸载钩子形态:`ctx.on` = 宿主生命周期事件;`apply-return` = 仅靠 apply 返回值(宿主据 _execute 登记) */
        readonly unloadHook: 'ctx.on' | 'apply-return';
      }
      
      /** 子插件的可调用面(鸭子类型:apply 可调用 + 可选的 inject/name;不 import cordis / 宿主类型) */
      type SubPlugin = {
        apply?: (c: unknown, config?: unknown) => unknown;
        inject?: readonly string[];
        name?: string;
      };
      
      /**
       * 宿主 Cordis 上下文的最小结构面(鸭子类型)。
       * 🔴 刻意不 import cordis / 宿主 SDK 的类型——只用结构描述(适配层红线)。
       */
      interface HostCtx {
        /** 宿主惯用法:挂载一个插件,返回 ForkScope(含 dispose)或 disposer */
        plugin?: (p: SubPlugin, config?: unknown) => unknown;
        /** 宿主生命周期事件(unload 钩子) */
        on?: (event: string, listener: (...a: unknown[]) => unknown) => unknown;
        provide?: (name: string, service: unknown) => unknown;
        sofagent?: Record<string, unknown>;
        [key: string]: unknown;
      }
      
      
      /**
       * 跨模块形态取「插件对象」。
       *
       * 🔴 为什么不能直接写 `mod.default?.apply`(实测结论,见 tools/gen 与本批汇报):
       *    6 个原子插件是 TS 编译出的 **CJS** 模块(`exports.default = kit.plugin` + `__esModule`),
       *    而 `await import(pkg)` 在 **CJS 里被 TS 保留为原生动态 import**(`module: node16`)。
       *    Node 对 CJS 的 ESM 互操作把 `default` 指向 `module.exports` 整体,于是
       *      · `mod.default`            = `{ __esModule, default: kit.plugin, pluginMeta, … }`
       *      · `mod.default.apply`      = **undefined**  ← 直呼只会静默空转(?. 兜住,不报错也不生效)
       *      · `mod.default.default.apply` = kit.plugin.apply ← 真正的插件面
       *    故这里逐层解一层 default;对「真 ESM(default 即插件)」形态同样成立。
       */
      function pluginOf(mod: unknown): SubPlugin {
        const layer1 = ((mod ?? {}) as { default?: unknown }).default ?? mod;
        const layer2 = ((layer1 ?? {}) as { default?: unknown }).default ?? layer1;
        return (layer2 ?? {}) as SubPlugin;
      }
      
      /**
       * DSH Cordis 插件契约:一次 apply 把 6 个原子插件逐个挂到同一个 ctx 上。
       *
       * 🔴 挂载路径(A2 修复):**优先宿主惯用法 `ctx.plugin(插件)`**——由宿主按各原子插件自己声明的
       *    `inject` 做就绪门控,并把它挂成独立 fiber(卸载时宿主按 fiber 托管反注册其服务);
       *    宿主无 `ctx.plugin`(裸 ctx)或宿主拒收时才**回落直呼 `apply`**,且仅在 apply 真正可调用时执行。
       *
       * 🔴 卸载路径:apply 返回一条幂等复合 disposer(宿主的 `Fiber._execute` 把「apply 返回函数」
       *    登记为 effect disposer);宿主另有 `ctx.on` 时再登记一条 `dispose` 监听做双保险(幂等,重复触发只生效一次)。
       *
       * 🔴 适配层红线(与 plugin-kit 同口径):`ctx: unknown` + 运行时鸭子类型,
       *    不 import cordis / 宿主 SDK 的**类型**;宿主 API 缺席时降级不抛。
       *
       * @param ctx 宿主上下文(鸭子类型:plugin / on / provide 任一缺席都不崩)
       * @returns 幂等复合 disposer(卸载本层时反注册它经手的服务)
       */
      export default {
        async apply(ctx: unknown): Promise<() => Promise<void>> {
          const c = (ctx ?? {}) as HostCtx;
          const loaded: string[] = [];
          const failed: Array<{ name: string; reason: string }> = [];
          const viaHost: string[] = [];
          const viaDirect: string[] = [];
          /** 卸载时逆序执行的 disposer(子插件句柄 / 宿主注册 API 返回值 / 本层命名空间条目) */
          const disposers: Array<() => unknown> = [];
          /** 收集宿主返回的可卸载句柄:函数直接用;ForkScope 形态取 .dispose */
          const collect = (ret: unknown): void => {
            if (typeof ret === 'function') {
              disposers.push(ret as () => unknown);
              return;
            }
            const handle = ret as { dispose?: unknown } | null | undefined;
            if (handle && typeof handle.dispose === 'function') {
              disposers.push(() => (handle.dispose as () => unknown).call(handle));
            }
          };
          const mountViaHost = typeof c.plugin === 'function';
      
          for (const [key, pkg] of SUITE) {
            let sub: SubPlugin;
            try {
              const mod = await import(pkg); // 懒加载:缺哪个报哪个,不整挂失败
              sub = pluginOf(mod);
            } catch (err) {
              // 逐个降级:单个原子插件缺席(未装 / 未 build)不阻断其余 5 个
              failed.push({ name: key, reason: err instanceof Error ? err.message : String(err) });
              continue;
            }
      
            // ① 宿主惯用法优先:拿到 inject 就绪门控 + 独立 fiber + 宿主托管卸载
            let hostReject: string | null = null;
            if (mountViaHost) {
              try {
                collect(c.plugin?.(sub));
                loaded.push(key);
                viaHost.push(key);
                continue;
              } catch (err) {
                hostReject = `宿主 ctx.plugin 拒收:${err instanceof Error ? err.message : String(err)}`;
              }
            }
      
            // ② 回落直呼 apply —— 仅当 apply **真正可调用**(apply 缺席时不得计入 loaded:那就是静默空转)
            if (typeof sub.apply !== 'function') {
              failed.push({
                name: key,
                reason: hostReject ? `${hostReject};且插件无 apply(不可调用)` : '插件无 apply(不可调用)——未挂载',
              });
              continue;
            }
            try {
              collect(await sub.apply(ctx));
              loaded.push(key);
              viaDirect.push(key);
            } catch (err) {
              const reason = err instanceof Error ? err.message : String(err);
              failed.push({ name: key, reason: hostReject ? `${hostReject};回落直呼亦失败:${reason}` : reason });
            }
          }
      
          // ③ 卸载路径:先建幂等 disposer(闭包按引用读 disposers,故此处可先于「本层服务注册」定义)
          let disposed = false;
          const dispose = async (): Promise<void> => {
            if (disposed) return;
            disposed = true;
            for (const undo of disposers.splice(0).reverse()) {
              try {
                await undo();
              } catch (err) {
                console.error('[sofagent-suite] 卸载失败:', err instanceof Error ? err.message : String(err));
              }
            }
          };
      
          // ④ 宿主生命周期钩子(鸭子类型)——与 apply 返回值双保险
          let unloadHook: SuiteReport['unloadHook'] = 'apply-return';
          if (typeof c.on === 'function') {
            try {
              c.on('dispose', dispose);
              unloadHook = 'ctx.on';
            } catch {
              // 为何可静默:apply 返回值仍在——宿主按 _execute 登记 disposer,
              // c.on 只是双保险;登记失败不等于卸载能力缺失,无降级告警可报。
            }
          }
      
          // ⑤ 本层自己的报告服务(同样收 disposer,卸载时反注册)
          const report: SuiteReport = { loaded, failed, total: SUITE.length, capability, viaHost, viaDirect, unloadHook };
          if (typeof c.provide === 'function') {
            collect(c.provide('sofagent.suite', report));
          } else {
            c.sofagent = { ...(c.sofagent ?? {}), suite: report };
            disposers.push(() => {
              const now = (c.sofagent ?? {}) as Record<string, unknown>;
              const next: Record<string, unknown> = { ...now };
              delete next.suite;
              c.sofagent = next;
            });
          }
      
          return dispose;
        },
      };
      
  • cordis.patch.yml 632 B
    # sofagent cordis-plugin-sofagent bundle patch v1.5.2——注册为 DSH profile layer
    - insert:
        - id: sofagent-suite
          name: 'cordis-plugin-sofagent'
          inject: [settings, dynamicCordisRunner]
          config:
            seam: "non-seam:plugin-suite"    # 语义:非宿主事件接入(插件聚合)——一次 apply 逐个挂载 6 个原子插件;能力仍由各原子插件 provide,聚合层自身不挂任何生命周期
            description: "一次挂载 sofagent 全套能力(6 项一次到位:注入 · 审计与验收 · 经验 · 回溯 · 巡检 · FDE 三域)——一次性挂载 6 个原子插件"
    
  • package.json 1.8 KB
    {
      "name": "cordis-plugin-sofagent",
      "version": "1.5.2",
      "engines": {
        "node": ">=18"
      },
      "description": "一次挂载 sofagent 全套能力(6 项一次到位:注入 · 审计与验收 · 经验 · 回溯 · 巡检 · FDE 三域)(seam: non-seam:plugin-suite)——一次性挂载 6 个原子插件",
      "license": "MIT",
      "author": "KongFangXun",
      "main": "dist/index.js",
      "types": "dist/index.d.ts",
      "scripts": {
        "build": "tsc",
        "test": "vitest run src/index.test.ts"
      },
      "keywords": [
        "sofagent",
        "dsh",
        "cordis-plugin"
      ],
      "sofagent": {
        "type": "dsh-plugin",
        "family": "cordis",
        "seam": "non-seam:plugin-suite",
        "seamSemantics": "非宿主事件接入(插件聚合)——一次 apply 逐个挂载 6 个原子插件;能力仍由各原子插件 provide,聚合层自身不挂任何生命周期"
      },
      "//optionalDependencies": "v1.4.8 第8批:src/index.ts 逐个 await import('<兄弟插件 id>')(懒加载 + 逐个降级不抛——缺任一原子插件只记入 failed 数组,不整挂失败),聚合层自身零 @sofagent/* 依赖;对齐 root package.json F-18 optionalDependencies 先例。",
      "optionalDependencies": {
        "cordis-plugin-sofagent-inject": "1.5.2",
        "cordis-plugin-sofagent-audit": "1.5.2",
        "cordis-plugin-sofagent-evolve": "1.5.2",
        "cordis-plugin-sofagent-rollback": "1.5.2",
        "cordis-plugin-sofagent-daemon": "1.5.2",
        "cordis-plugin-sofagent-fde": "1.5.2"
      },
      "devDependencies": {
        "typescript": "^7.0.2",
        "vitest": "^5.0.0"
      },
      "dsh": {
        "bundle": {
          "patch": "./cordis.patch.yml"
        }
      },
      "files": [
        "dist/",
        "cordis.patch.yml",
        "SKILL.md",
        "!dist/**/*.js.map",
        "!dist/**/*.d.ts.map",
        "!dist/**/*.test.*"
      ]
    }
    
  • SKILL.md 2.3 KB
    ---
    name: cordis-plugin-sofagent
    slug: cordis-plugin-sofagent
    version: 1.5.2
    displayName: cordis-plugin-sofagent
    description: >
      一次挂载 sofagent 全套能力——6 项能力一次到位(注入 · 审计与验收 · 经验 · 回溯 · 巡检 · FDE 三域)(seam: non-seam:plugin-suite)——只编排不重实现——sofagent 约束层在 DSH(DeepSeek Harness)生态的插件形态。
    ---
    
    # cordis-plugin-sofagent
    
    一次挂载 sofagent 全套能力——6 项能力一次到位(注入 · 审计与验收 · 经验 · 回溯 · 巡检 · FDE 三域)(seam: non-seam:plugin-suite)——只编排不重实现,能力仍由各原子插件提供
    
    ## 用途
    
    一次 `apply` 把 6 个原子插件(inject / audit / evolve / rollback / daemon / fde)逐个挂到同一个 `ctx` 上,挂完把结果(`loaded` / `failed` / `total`)注册成 `sofagent.suite` 服务。适合「整套装上、不想逐个装」的场景。**什么时候用**:一次拿到全套约束层能力、不想逐个挑插件时。
    
    三条硬约束:
    
    1. **只编排,不重实现** —— 每个能力仍由原子插件 `provide`,本层只依次调用它们的 `apply`,不复制任何子插件逻辑;
    2. **逐个降级,不整挂失败** —— 缺任一个原子插件只记入 `failed` 数组(含包名与原因),其余 5 个照常加载;
    3. **不替代细粒度插件** —— 6 个原子插件全部保留,本插件是**新增的整装选项**,按需二选一。
    
    本插件随 sofagent 主线版本发布,走**宿主挂载通道**:`skillhub install cordis-plugin-sofagent`。⚠️ **本插件不在 npm 通道**——11 款插件(7 DSH + 4 OpenClaw,v1.4.9 P2 合并批 14→11)均为 `private`,npm 装不到;npm 侧另有**库通道** `npm i -g sofagent`(总包 `engine/umbrella`,聚合 `@sofagent/audit` / `mcp` / `orchestrator` / `daemon` 四个能力包,**不含本插件**)。两条通道按所在环境二选一,互不依赖、互不为前置。版本号与 sofagent 主线对齐。
    
    本插件是**宿主挂载入口**;**npm 安装入口见裸名总包**(`engine/umbrella`,包名 `sofagent`)—— 两个入口互不引用为前置,按使用场景各取其一。
    
    ## 相关链接
    
    - sofagent 主仓:https://github.com/KongFangXun/sofagent
    - 开发日志:docs/changelog/v1.4/v1.4.0.md(DSH 插件家族)
    
  • tsconfig.json 185 B
    {
      "extends": "../tsconfig.base.json",
      "compilerOptions": {
        "rootDir": "src",
        "outDir": "dist"
      },
      "include": [
        "src"
      ],
      "exclude": [
        "src/**/*.test.ts"
      ]
    }
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related