Claude Cursor Skill

cordis-plugin-sofagent-rollback

出错逆序撤销——git snapshot → effect disposer(seam: agent/error)——桥接 @sofagent/core getHistoryFilePath——DSH(DeepSeek Harness)cordis plugin。sofagent 约束层在 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-rollback-b8c9032.zip · 7 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-rollback
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-rollback

出错逆序撤销——git snapshot → effect disposer(seam: agent/error)——桥接 @sofagent/core getHistoryFilePath

用途

装上之后:Agent 出错时按 git 快照逆序撤销本次会话改动。什么时候用:Agent 一次动了大量文件、需要一键回到干净状态。

接入点(seam: agent/error):桥接 @sofagent/core,缺依赖时该能力静默跳过;接入形态(声明 / 实现)见 SEAMS.md。

本插件随 sofagent 主线版本发布(SkillHub 通道:skillhub install cordis-plugin-sofagent-rollback 安装与检索;npm 通道未开通)。版本号与 sofagent 主线对齐。

相关链接

  • sofagent 主仓:https://github.com/KongFangXun/sofagent
  • 开发日志:docs/changelog/v1.4/v1.4.0.md(DSH 插件家族)
Files (sofagent)
  • src
    • index.test.ts 7.6 KB
      // ============================================================
      // cordis-plugin-rollback · 插件单测(v1.4.0 交付五 · v1.5.0 章十补接线面)
      // ============================================================
      
      import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
      import pluginDefault, { pluginMeta, capability, invoke } from './index';
      import { listSnapshots, restoreSnapshot } from '@sofagent/core';
      
      // 章十:快照两源(listSnapshots → restoreSnapshot)打桩成确定性替身——
      // 接线正确性 = 定位口径(追加序取末条)与透传参数,与引擎内部实现无关。
      vi.mock('@sofagent/core', () => ({
        listSnapshots: vi.fn(() => [{ sha: 'sha-latest' }]),
        restoreSnapshot: vi.fn(async () => ['src/a.ts']),
      }));
      
      // 必要的 schemastery 替身:settings.register 分支的 require 会拿它构造 shape——缺席走 catch 降级
      vi.mock('@deepseek-ai/schemastery', () => ({
        boolean: () => ({ __type: 'boolean' }),
        string: () => ({ __type: 'string' }),
        object: (shape: unknown) => ({ __type: 'object', shape }),
      }));
      
      describe('cordis-plugin-sofagent-rollback', () => {
        it('插件元数据完整(id/version/description/seam)', () => {
          expect(pluginMeta.id).toBe('cordis-plugin-sofagent-rollback');
          expect(pluginMeta.version).toBe(require('../package.json').version); // SSOT 对齐:版本跟随主版本(读 package.json)
          expect(pluginMeta.description.length).toBeGreaterThan(10);
          expect(pluginMeta.seam.length).toBeGreaterThan(0);
        });
      
        it('能力说明非空', () => {
          expect(capability.length).toBeGreaterThan(0);
        });
      
        it('invoke 可调用(成功或降级,不挂死)', async () => {
          const r = await invoke().catch((e) => e);
          expect(r).toBeDefined();
        });
      });
      
      // ─────────────────────────────────────────────────────────────────────────────
      // v1.5.0 章十 · seam 事件接线(seam 从声明到实现)
      // 断言面:① 订阅真实存在 ② disposer 收进复合卸载契约
      //        ③ **默认关档不动作**(破坏性动作不得因接线而改变既有行为)
      //        ④ 显式开档才走 listSnapshots → restoreSnapshot(追加序取末条 = 最新)
      //        ⑤ 无快照 / 恢复失败 → 零动作、不抛(fail-open,不阻断宿主错误处理)
      // ─────────────────────────────────────────────────────────────────────────────
      describe('章十 · seam 事件接线', () => {
        const MANIFEST = require('../../plugins.json') as {
          plugins: Array<{
            id: string;
            seamHandlers?: string[];
            bridges?: Array<{ pkg: string; api: string }>;
          }>;
        };
        const entry = MANIFEST.plugins.find((p) => p.id === 'cordis-plugin-sofagent-rollback');
        const declared: string[] = entry?.seamHandlers ?? [];
        const ONE = ['agent/error'];
      
        const errSpy = () => vi.spyOn(console, 'error').mockImplementation(() => {});
      
        beforeEach(() => {
          vi.clearAllMocks();
          process.env.SOFAGENT_PROJECT_ROOT = '/proj';
          vi.mocked(listSnapshots).mockImplementation(() => [{ sha: 'sha-latest' }]);
          vi.mocked(restoreSnapshot).mockImplementation(async () => ['src/a.ts']);
        });
        afterEach(() => {
          vi.restoreAllMocks();
          delete process.env.SOFAGENT_PROJECT_ROOT;
        });
      
        /**
         * 宿主 ctx 替身。
         * @param flag 档位值——`'true'` 显式开档;`'false'` 显式关档;`undefined` 走声明默认值(关)
         */
        function hostCtx(flag?: string) {
          const subscribed = new Map<string, (...a: unknown[]) => unknown>();
          const disposed: string[] = [];
          const on = vi.fn((event: string, handler: (...a: unknown[]) => unknown) => {
            subscribed.set(event, handler);
            return () => {
              disposed.push(event);
            };
          });
          const settings =
            flag === undefined
              ? undefined
              : {
                  register: vi.fn(() => ({
                    get: () => ({ rollbackOnError: flag }),
                    watch: () => () => undefined,
                  })),
                };
          return { ctx: { provide: vi.fn(() => () => undefined), on, settings }, subscribed, disposed };
        }
      
        const errorPayload = () => ({ turn: 2, step: 3, error: new Error('agent 崩了') });
      
        it('seamHandlers 与 plugins.json 声明同集合(三处对账的插件侧)', () => {
          expect(declared.slice().sort()).toEqual([...ONE].sort());
        });
      
        it('bridges 声明覆盖快照两源(listSnapshots / restoreSnapshot)', () => {
          const keys = (entry?.bridges ?? []).map((b) => `${b.pkg}.${b.api}`).sort();
          expect(keys).toEqual(['@sofagent/core.listSnapshots', '@sofagent/core.restoreSnapshot'].sort());
        });
      
        it('apply 经 ctx.on 订阅 agent/error;复合 disposer 逐个反注册', async () => {
          const spy = errSpy();
          const { ctx, subscribed, disposed } = hostCtx();
          const disposer = (pluginDefault.apply as (c: unknown) => unknown)(ctx) as
            | (() => Promise<void>)
            | undefined;
          expect([...subscribed.keys()].sort()).toEqual([...ONE].sort());
          expect(typeof disposer).toBe('function');
          await disposer!();
          expect(disposed.slice().sort()).toEqual([...ONE].sort());
          spy.mockRestore();
        });
      
        it('默认关档(无 settings 面):订阅在位但零动作——接线不改变既有行为', async () => {
          const spy = errSpy();
          const { ctx, subscribed } = hostCtx();
          (pluginDefault.apply as (c: unknown) => unknown)(ctx);
      
          await expect(subscribed.get('agent/error')!(errorPayload())).resolves.toBeUndefined();
      
          expect(listSnapshots).not.toHaveBeenCalled();
          expect(restoreSnapshot).not.toHaveBeenCalled();
          spy.mockRestore();
        });
      
        it("显式关档(rollbackOnError = 'false'):有快照也不撤销", async () => {
          const spy = errSpy();
          const { ctx, subscribed } = hostCtx('false');
          (pluginDefault.apply as (c: unknown) => unknown)(ctx);
      
          await subscribed.get('agent/error')!(errorPayload());
      
          expect(listSnapshots).not.toHaveBeenCalled();
          expect(restoreSnapshot).not.toHaveBeenCalled();
          spy.mockRestore();
        });
      
        it('显式开档:取追加序末条(最新)快照恢复,参数逐字透传', async () => {
          const spy = errSpy();
          vi.mocked(listSnapshots).mockImplementation(() => [{ sha: 'sha-old' }, { sha: 'sha-latest' }]);
          const { ctx, subscribed } = hostCtx('true');
          (pluginDefault.apply as (c: unknown) => unknown)(ctx);
      
          await subscribed.get('agent/error')!(errorPayload());
      
          expect(listSnapshots).toHaveBeenCalledWith('/proj');
          expect(restoreSnapshot).toHaveBeenCalledWith('/proj', 'sha-latest'); // 末尾即最新
          spy.mockRestore();
        });
      
        it('显式开档但无可用快照:不撤销(先跑一次审计才会建快照)', async () => {
          const spy = errSpy();
          vi.mocked(listSnapshots).mockImplementation(() => []);
          const { ctx, subscribed } = hostCtx('true');
          (pluginDefault.apply as (c: unknown) => unknown)(ctx);
      
          await subscribed.get('agent/error')!(errorPayload());
      
          expect(restoreSnapshot).not.toHaveBeenCalled();
          spy.mockRestore();
        });
      
        it('显式开档但恢复失败:不抛(fail-open,不阻断宿主错误处理)', async () => {
          const spy = errSpy();
          vi.mocked(restoreSnapshot).mockImplementation(async () => {
            throw new Error('快照损坏');
          });
          const { ctx, subscribed } = hostCtx('true');
          (pluginDefault.apply as (c: unknown) => unknown)(ctx);
      
          await expect(subscribed.get('agent/error')!(errorPayload())).resolves.toBeUndefined();
          spy.mockRestore();
        });
      });
      
    • index.ts 4.2 KB
      // cordis-plugin-sofagent-rollback · DSH 反向插件(v1.5.2:98 行样板收敛到 @sofagent/dsh-plugin-kit)
      // seam 挂载:agent/error    # 语义:Agent 出错 → git snapshot 逆序撤销(默认关档)
      // 清单生成源 = engine/dsh-plugins/plugins.json(生成 package.json 的 description/sofagent/dsh 段与 cordis.patch.yml);本文件的 seam 字面量由生成器 --check 与之对账。
      
      import {
        createSofagentPlugin,
        seamHelpers,
        type SeamHandler,
        type SeamHelpers,
      } from '@sofagent/dsh-plugin-kit';
      
      /** 一次性日志表(接线自证 / 降级提示只打一次) */
      const logged = new Set<string>();
      function logOnce(helpers: SeamHelpers, key: string, message: string): void {
        if (logged.has(key)) return;
        logged.add(key);
        helpers.log(message);
      }
      
      const errMsg = (err: unknown): string => (err instanceof Error ? err.message : String(err));
      
      /** 项目根:显式环境变量优先,缺省取宿主进程工作目录 */
      const projectRoot = (): string => process.env.SOFAGENT_PROJECT_ROOT ?? process.cwd();
      
      /**
       * seam 事件处理器(v1.5.0 章十)——`agent/error` 的**实现**(此前只有声明)。
       *
       * 宿主契约:emit 派发,listener 约定 `(payload)`,payload 含 `{ agent, turn, step, error }`;
       * 返回值无意义(观察位)。
       *
       * 🔴 **默认关档**(settings `rollbackOnError = 'false'`):自动撤销工作区是破坏性
       *    动作,接线本身不应改变既有行为——要恢复就必须显式开档。关档时本事件位仍
       *    真实订阅(装载状态可自证),只是不动作。
       * 🔴 判定逻辑零改动:快照定位与恢复都是既有 @public API(`listSnapshots` /
       *    `restoreSnapshot`),插件只做串联;快照序为追加序,「末尾即最新」。
       */
      const seamHandlers: Record<string, SeamHandler> = {
        'agent/error': async (...args: unknown[]) => {
          const helpers = seamHelpers(args);
          if (helpers.flags().rollbackOnError !== true) {
            logOnce(
              helpers,
              'error-rollback-disarmed',
              'Agent 出错事件已接线;自动回滚档位关闭(rollbackOnError=false)——不撤销工作区(默认关,避免误伤未提交变更)',
            );
            return;
          }
          try {
            const root = projectRoot();
            const snapshots = await helpers.call('@sofagent/core', 'listSnapshots', root);
            if (!Array.isArray(snapshots) || snapshots.length === 0) {
              logOnce(helpers, 'error-no-snapshot', 'Agent 出错但无可用快照——不撤销(先跑一次审计才会建快照)');
              return;
            }
            const latest = snapshots[snapshots.length - 1] as { sha?: unknown };
            if (typeof latest?.sha !== 'string' || latest.sha === '') return;
            const restored = await helpers.call('@sofagent/core', 'restoreSnapshot', root, latest.sha);
            helpers.log(
              `出错逆序撤销:已恢复到快照 ${latest.sha.slice(0, 8)}(${Array.isArray(restored) ? restored.length : 0} 个文件)`,
            );
          } catch (err) {
            logOnce(helpers, 'error-rollback-failed', `快照回滚失败(不阻断宿主错误处理):${errMsg(err)}`);
          }
        },
      };
      
      /** 插件声明(本文件唯一手写处;适配层红线由 kit 承担:ctx 鸭子类型 + 宿主 API 缺席降级不抛) */
      const kit = createSofagentPlugin(
        {
          id: 'cordis-plugin-sofagent-rollback',
          seam: 'agent/error',
          seamSemantics: 'Agent 出错 → git snapshot 逆序撤销(默认关档,需显式开 rollbackOnError)',
          capability: '快照回溯(出事一键回滚)',
          bridgePkg: '@sofagent/core',
          bridgeApi: 'restoreSnapshot',
          description: '出错逆序撤销——git snapshot → effect disposer',
          // 默认关:自动撤销工作区是破坏性动作,接线不改变既有行为
          settingsExtra: { rollbackOnError: 'false' },
          seamHandlers,
        },
        require('../package.json') as { version?: string },
      );
      
      export const pluginMeta = kit.pluginMeta; // 插件元数据(DSH profile/注册表消费)
      export const capability = kit.capability; // 依赖的 sofagent 能力说明(DSH skill 引导链展示)
      export const invoke = kit.invoke; // 桥接 @sofagent/* 公共 API(懒加载 + 降级不抛)
      export default kit.plugin; // DSH Cordis 插件契约(apply 三段式由 kit 提供)
      
  • cordis.patch.yml 558 B
    # sofagent cordis-plugin-sofagent-rollback bundle patch v1.5.2——注册为 DSH profile layer
    - insert:
        - id: sofagent-rollback
          name: 'cordis-plugin-sofagent-rollback'
          inject: [settings, dynamicCordisRunner]
          config:
            seam: "agent/error"    # 语义:Agent 出错 → git snapshot 逆序撤销(默认关档,需显式开 rollbackOnError)
            description: "给宿主加上快照回溯——Agent 出错时按 git 快照逆序撤销本次改动——桥接 @sofagent/core restoreSnapshot / @sofagent/core listSnapshots"
    
  • package.json 1.6 KB
    {
      "name": "cordis-plugin-sofagent-rollback",
      "version": "1.5.2",
      "engines": {
        "node": ">=18"
      },
      "description": "给宿主加上快照回溯——Agent 出错时按 git 快照逆序撤销本次改动(seam: agent/error)——桥接 @sofagent/core restoreSnapshot / @sofagent/core listSnapshots",
      "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": "agent/error",
        "seamSemantics": "Agent 出错 → git snapshot 逆序撤销(默认关档,需显式开 rollbackOnError)",
        "seamHandlers": [
          "agent/error"
        ]
      },
      "//optionalDependencies": "v1.4.5 T6 (R4):src/index.ts 惰性 await import(懒加载 + 缺依赖降级不抛;v1.4.8 起该样板由 @sofagent/dsh-plugin-kit 统一封装;v1.4.9 P2 起厚插件多包 bridges 按序解析、部分可用即部分成功),此前未声明任何依赖——对齐 root package.json F-18 optionalDependencies 先例。",
      "optionalDependencies": {
        "@sofagent/core": "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.*"
      ],
      "dependencies": {
        "@sofagent/dsh-plugin-kit": "1.5.2"
      }
    }
    
  • SKILL.md 1.2 KB
    ---
    name: cordis-plugin-sofagent-rollback
    slug: cordis-plugin-sofagent-rollback
    version: 1.5.2
    displayName: cordis-plugin-sofagent-rollback
    description: >
      出错逆序撤销——git snapshot → effect disposer(seam: agent/error)——桥接 @sofagent/core getHistoryFilePath——DSH(DeepSeek Harness)cordis plugin。sofagent 约束层在 DeepSeek Harness 生态的插件形态。
    ---
    
    # cordis-plugin-sofagent-rollback
    
    出错逆序撤销——git snapshot → effect disposer(seam: agent/error)——桥接 @sofagent/core getHistoryFilePath
    
    ## 用途
    
    **装上之后**:Agent 出错时按 git 快照逆序撤销本次会话改动。**什么时候用**:Agent 一次动了大量文件、需要一键回到干净状态。
    
    **接入点**(seam: agent/error):桥接 `@sofagent/core`,缺依赖时该能力静默跳过;接入形态(声明 / 实现)见 [SEAMS.md](../SEAMS.md)。
    
    本插件随 sofagent 主线版本发布(SkillHub 通道:`skillhub install cordis-plugin-sofagent-rollback` 安装与检索;npm 通道未开通)。版本号与 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