欢迎使用Markdown编辑器

你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。

新的改变

我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:

  1. 全新的界面设计 ,将会带来全新的写作体验;
  2. 在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的高亮样式 进行展示;# 让所有 Agent 都会 /goal:HagiCode 持续工作预设的兼容扩展设计

引言

给 AI Agent 派一个「持续工作」的任务,理想状态是这样的:你写下一个目标,选好可以动的仓库,点一下提交,Agent 就朝着这个目标一路推进,中途不追问、不跑题、不越界改别的仓库。

在 HagiCode 里,这个能力有个专门的入口——goal 预设任务,命令表面是 /goal。问题在于:不是所有 Agent 都认识 /goal Claude 和 Codex 有原生的持续工作命令语义,/goal 递过去它们能直接接住;而其它 CLI(Gemini、Copilot、iFlow、OpenCode 等)根本没有这个斜杠命令,把 /goal ... 原样塞过去,轻则被当成无效命令,重则被逐字执行踩坑。

一开始最省事的做法是:干脆只让 goal 跑在 Claude / Codex 上,其它 Agent 一律拒绝。但这等于把大半个 Agent 矩阵挡在门外。本文讲的就是我们怎么把这条路走通——用「Agent 感知」的提示变体(prompt variants),让原生支持的 Agent 走原生命令,不支持的 Agent 走兜底提示,行为对齐、体验一致。

内容基于 repos/hagitask 里的 goal 预设包和 repos/hagicode-core 的后端解析实现。

背景

goal 是什么

goal 是一个内置的任务预设插件包,位于 repos/hagitask/presets/goal/。它的 manifest.json 声明了插件身份、图标、本地化包和前后端资产:

{
  "taskPresetId": "goal",
  "version": "1.0.0",
  "icon": "flag",
  "kind": "custom-executor",
  "status": "experimental",
  "entrypoints": { "menuSurface": "session-create", "drawerId": "goal" },
  "ui": { "panel": "./frontend/panel.json", "commands": "./frontend/commands.json" },
  "backend": {
    "taskPreset": "./backend/task-preset.json",
    "prompts": "./backend/prompts.json"
  }
}

它的定位是「持续工作模式」:用户填一个目标说明(goalDescription),可选地圈定可写 / 只读仓库范围,然后由后端组装成一次非交互的 auto-task 运行。前端面板 panel.json 里的关键字段是:

  • goalCommandIds:命令选择器,内置只有一个 /goal
  • goalDescription:必填的目标说明;
  • targetRepositories:仓库访问选择器,区分 read / write。

/goal 从哪来

命令表面定义在 frontend/commands.json 里,核心是一行 preludeTemplate

{
  "commands": [
    {
      "id": "goal",
      "label": "goal",
      "preludeTemplate": "/goal {goalDescription}"
    }
  ]
}

也就是说,最终发给 Agent 的提示,会在正文前面拼一行 /goal <目标说明>。对 Claude / Codex,这一行就是它们认识的命令;对别的 Agent,这一行就是个需要「被翻译」的东西。整篇文章的适配难点,正是这一行 /goal ... 在不同 Agent 上的含义分裂。

分析

单 locale 模板撑不住了

HagiTask 早期的提示包(prompt package)是只按语言分文件的:prompts.json 里每个 locale 声明一套 systemTemplate + userTemplate,后端 PresetTaskCatalogProvider 把它们读进 PromptPackage.Locales,运行时按请求语言选一套渲染。

这个模型对「执行指令与 Agent 无关」的预设够用。但 goal 不是这样:

  • Claude / Codex:可以直接依赖原生 /goal 命令表面;
  • 其它 Agent:需要一段更啰嗦、显式教它「怎么进入持续工作模式」的提示,还得告诉它别把 /goal ... 当命令执行。

同一个 locale 下要出现两种截然不同的指令,只按语言分文件就不够了。我们需要在语言之外再引入一个维度:Agent 家族

拒绝执行不是好方案

最初 goalrequirements 把 Agent 限死在 ["claude", "codex"],其它 Agent 直接判不兼容。这在产品上有两个坏处:

  1. 用户在英雄(hero)选择面板里,兼容英雄被过滤得很少,容易「无雄可选」;
  2. 明明很多 Agent 有能力完成持续工作,只是没有原生 /goal 而已,一刀切拒绝浪费了它们的能力。

所以真正要解决的问题不是「谁能跑」,而是「同一个目标,如何按 Agent 能力给出不同但等价的执行指令」。答案是:把 Agent 感知的差异下沉到提示层,用变体 + 兜底解决,而不是在准入层拦人。

解决

变体选择器:agent × language

新的提示包合同在 prompts.json 里引入 variants,用 selectors 声明匹配维度,用 entries 声明每个变体的匹配规则和模板文件:

{
  "version": "1.1.0",
  "templateEngine": "handlebars",
  "variants": {
    "selectors": ["agent", "language"],
    "entries": [
      {
        "id": "native-zh-cn",
        "match": { "agent": ["claude", "codex"], "language": ["zh-CN"] },
        "systemTemplate": "./templates/zh-CN/native.system.md",
        "userTemplate": "./templates/zh-CN/native.user.hbs"
      },
      {
        "id": "fallback-zh-cn",
        "match": { "language": ["zh-CN"] },
        "systemTemplate": "./templates/zh-CN/fallback.system.md",
        "userTemplate": "./templates/zh-CN/fallback.user.hbs"
      }
      // ... native-en-us / fallback-en-us 同理
    ],
    "fallback": {
      "id": "fallback-default",
      "systemTemplate": "./templates/en-US/fallback.system.md",
      "userTemplate": "./templates/en-US/fallback.user.hbs"
    }
  }
}

三个层次的兜底,逐级放宽:

  1. native 变体match 同时约束 agent(claude/codex)和 language,只有原生 Agent 命中;
  2. fallback 变体match 只约束 language,不约束 agent,于是任何 Agent 都能命中——这是给「没有原生 /goal」的 Agent 兜底的;
  3. fallback-default:连语言都匹配不上时的最终保底。

顺序很关键:entries 是按声明顺序遍历的,native 排在 fallback 前面。所以 claude/codex 会先命中 native;其它 Agent 走到 fallback 语言变体。

两套模板:原生 vs 兜底

同一个目标,两套 system 提示的差别就在对 /goal 那一行的态度。

原生模板(templates/zh-CN/native.system.md):

你是 `goal` task preset plugin 的内置执行提示。

本次运行必须使用 `/goal` 作为持续工作模式的命令表面。把提供的项目路径与仓库范围视为权威工作边界。

这次运行处于非交互环境。不要追问用户;当合理假设可以继续推进时,直接继续并把这些假设明确写在响应里。

兜底模板(templates/zh-CN/fallback.system.md):

你是 `goal` task preset plugin 的兜底执行提示。

当前 agent 可能不支持原生 `/goal` 命令。如果提示前面出现了 `/goal ...` 这一行,把它视为预设附带的目标元数据,不要把它当成必须逐字执行的斜杠命令。

请以持续工作模式完成任务,并把提供的项目路径与仓库范围视为权威工作边界。

这次运行处于非交互环境。不要追问用户;当合理假设可以继续推进时,直接继续并把这些假设明确写在响应里。

核心那句话就是兜底策略的灵魂:「如果提示前面出现了 /goal ... 这一行,把它视为预设附带的目标元数据,不要把它当成必须逐字执行的斜杠命令。」 前面 preludeTemplate 拼进去的那行 /goal <目标>,在原生 Agent 眼里是命令,在兜底 Agent 眼里被显式降级成「元数据 / 目标标记」,从而用普通的提示跟随(prompt following)复现同样的持续工作行为。

user 模板(.hbs)也分两套,兜底那套多了「路由说明」段落,明确告诉 Agent 这次被路由到了不依赖原生 /goal 的分支,并要求「不要依赖原生 /goal 支持;改用普通提示跟随方式完成同样的持续工作行为」。

后端:一次解析定生死

变体匹配在后端 PCode.Application/Services/PresetTaskCatalogProvider.csResolvePromptSelection 里完成。它先解析 locale,再把 language、agent 装进一个选择器字典,按顺序找第一个匹配的变体:

public PresetTaskResolvedPromptSelection ResolvePromptSelection(string? locale, string? agentFamily)
{
    var resolvedLocale = ResolvePromptLocale(locale);
    if (!PromptPackage.UsesVariants)
    {
        // 旧的仅按 locale 分文件的预设,走遗留路径,保持向后兼容
        var legacyTemplate = PromptPackage.Locales[resolvedLocale];
        return new PresetTaskResolvedPromptSelection(
            resolvedLocale, NormalizeAgentFamily(agentFamily), null, false,
            "legacy-locales", legacyTemplate);
    }

    var normalizedAgentFamily = NormalizeAgentFamily(agentFamily);
    var selectorInputs = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
    {
        [PresetTaskPromptSelectorNames.Language] = resolvedLocale,
    };
    if (!string.IsNullOrWhiteSpace(normalizedAgentFamily))
    {
        selectorInputs[PresetTaskPromptSelectorNames.Agent] = normalizedAgentFamily!;
    }

    foreach (var variant in PromptPackage.Variants)
    {
        if (!VariantMatches(variant, selectorInputs)) continue;
        return new PresetTaskResolvedPromptSelection(
            resolvedLocale, normalizedAgentFamily, variant.Id, false, "variants", variant.Template);
    }

    // 没有变体命中:落到显式声明的 fallback 变体
    return new PresetTaskResolvedPromptSelection(
        resolvedLocale, normalizedAgentFamily, PromptPackage.FallbackVariant?.Id, true,
        "variants", PromptPackage.FallbackVariant?.Template ?? PromptPackage.Locales[resolvedLocale]);
}

匹配规则 VariantMatches 很朴素——变体声明了哪些 selector,运行时就都得命中;没声明的维度不约束:

private static bool VariantMatches(
    PresetTaskPromptVariantDefinition variant,
    IReadOnlyDictionary<string, string> selectorInputs)
{
    foreach (var selector in variant.Match)
    {
        if (!selectorInputs.TryGetValue(selector.Key, out var runtimeValue))
            return false;
        if (!selector.Value.Contains(runtimeValue, StringComparer.OrdinalIgnoreCase))
            return false;
    }
    return true;
}

这就解释了为什么 fallback 变体只写 language 就能兜住所有 Agent:它根本没约束 agent 维度,任何 Agent 的 language 一致就命中。而 native 变体多约束了一个 agent,只有 claude/codex 能满足。

Agent 家族怎么来的?运行时从选中的英雄执行器类型映射,见 PresetTaskRequirementModels.cs

public static string? Resolve(AIProviderType? executorType) => executorType switch
{
    AIProviderType.ClaudeCodeCli => Claude,
    AIProviderType.CodexCli      => Codex,
    AIProviderType.GitHubCopilot => Copilot,
    AIProviderType.IFlowCli      => Iflow,
    AIProviderType.OpenCodeCli   => Opencode,
    // ... gemini / kimi / qoder / kiro / hermes / codebuddy / pi ...
    _ => null,
};

SessionsController.PresetTasks.cs 在创建会话时先拿到英雄,再 PresetTaskAgentFamilies.Resolve(hero.ExecutorType) 得到家族名,喂给 ResolvePromptSelection。选中变体后,RenderPrompt 会用 Handlebars 渲染模板,并把 preludeTemplate 生成的 /goal <目标> 拼到最前面:

var renderedPrompt = prompt.FillTemplate(normalizedContext);
var commandPrelude = BuildCommandPrelude(normalizedContext); // -> "/goal <goalDescription>"
return string.IsNullOrWhiteSpace(commandPrelude)
    ? renderedPrompt
    : $"{commandPrelude}\n\n{renderedPrompt}";

准入放宽:从 allowlist 到 any

有了兜底提示,goal 就没必要再把非原生 Agent 拒之门外了。backend/task-preset.jsonrequirements["claude","codex"] 放宽成 any

{
  "taskKey": "goal",
  "requirements": [ { "type": "agent", "name": "any" } ],
  "inputBindings": [
    { "input": "goalCommandIds",  "promptParameter": "goalCommandIds",  "required": true },
    { "input": "goalDescription", "promptParameter": "goalDescription", "required": true },
    { "input": "targetScopeMarkdown", "promptParameter": "targetScopeMarkdown", "producer": "frontend-computed" }
  ]
}

准入层放开、差异下沉到提示层——这是整个方案的关键取舍:兼容性不再是「能不能跑」的开关,而是「用哪套提示跑」的路由。

实践

端到端一次运行是怎样的

  1. 用户在 goal 抽屉里填目标说明、选仓库范围、选英雄,提交;
  2. 后端按选中英雄的执行器类型解析出 agent 家族(如 claude / gemini);
  3. ResolvePromptSelection(locale, agentFamily)agent × language 找变体:
    • claude/codex + zh-CN → native-zh-cn
    • gemini + zh-CN → native 不命中,落到 fallback-zh-cn
  4. RenderPrompt 渲染选中模板,并在最前面拼 /goal <目标说明>
  5. 原生 Agent 把首行当命令执行;兜底 Agent 按 system 提示把首行当元数据,用普通提示跟随复现持续工作。

给自己的预设加 Agent 感知

如果你要给一个新预设做同样的适配,套路是固定的:

  1. prompts.json 里用 variants 替代 localesselectors 里加上 agent
  2. native 变体 match 同时写 agentlanguage;兜底变体只写 language
  3. 再声明一个顶层 variants.fallback 作为最终保底(schema 要求 variants 模式必须有 fallback);
  4. 后端不用改——ResolvePromptSelection / VariantMatches 是通用的;
  5. 若准入不该限制,就把 requirements 的 agent 设为 any

几个容易踩的坑

  • 变体顺序即优先级。 native 必须排在 fallback 前,否则只约束 language 的 fallback 会先把所有 Agent 抢走,claude/codex 永远走不到原生分支。
  • 兜底提示必须显式「降级」/goal 那行。 前缀 /goal ... 是无条件拼上去的(由 preludeTemplate 决定),所以兜底 system 提示里那句「把它当元数据、别逐字执行」不能省,否则非原生 Agent 可能把它当无效命令处理。
  • 非交互约束要写进每套模板。 goal 是排队执行的 auto-task,模板里反复强调「不要用 AskUserQuestion、不要追问、用合理假设继续并写明」,就是防止 Agent 在无人值守时卡在等待输入。
  • 仓库边界靠提示约束,不是靠沙箱。 模板明确要求「仅在显式选择为可写的仓库内修改,只读仓库当补充上下文」。这属于提示层的软约束,选仓库范围时要认真填。
  • 向后兼容有免费午餐。 老预设不写 variants、只写 locales 时,UsesVariants 为 false,直接走 legacy-locales 路径,完全不受影响,不用为了新模型改存量预设。

总结

「如果 Agent 原生不支持 goal,我们怎么扩展来实现」——答案不是给每个 Agent 写死一堆分支,而是把差异建模成 agent × language 的提示变体:

  • 原生支持的 Agent(Claude / Codex)走 native 变体,直接用 /goal 命令表面;
  • 不支持的 Agent 走 fallback 变体,把 /goal ... 显式降级为目标元数据,用普通提示跟随复现同样的持续工作行为;
  • 匹配靠「变体声明哪些 selector 就必须命中哪些」的通用规则 + 逐级兜底,后端一次解析定型;
  • 准入从 allowlist 放宽到 any,兼容性从「能不能跑」变成「用哪套提示跑」。

这套设计的价值不只在 goal 一个预设上:agent × language 的选择器 + 兜底模型是通用的,任何需要「按 Agent 能力给不同指令」的预设都能复用,而且对老预设零打扰。

参考资料

  • 预设包:repos/hagitask/presets/goal/manifest.jsonbackend/task-preset.jsonbackend/prompts.jsonbackend/templates/{en-US,zh-CN}/{native,fallback}.*frontend/commands.jsonfrontend/panel.json
  • 提示包 schema:repos/hagitask/schemas/task-preset-plugin/prompt-package.schema.json
  • 后端变体解析:repos/hagicode-core/src/PCode.Application/Services/PresetTaskCatalogProvider.csResolvePromptSelection / VariantMatches / RenderPrompt / BuildCommandPrelude
  • Agent 家族映射:repos/hagicode-core/src/PCode.Application/Services/PresetTaskRequirementModels.csPresetTaskAgentFamilies.Resolve
  • 会话创建:repos/hagicode-core/src/PCode.HttpApi/Controllers/SessionsController.PresetTasks.cs
  • OpenSpec 能力与提案:openspec/specs/goal-preset-task/spec.mdopenspec/changes/archive/2026-06-23-preset-task-agent-requirement-support/openspec/changes/archive/2026-06-26-support-agent-language-prompt-selection-in-hagitask/

原文与版权说明

感谢您的阅读,如果您觉得本文有用,欢迎点赞、收藏和分享支持。
本内容采用人工智能辅助协作,最终内容由作者审核并确认。

  1. 增加了 图片拖拽 功能,你可以将本地的图片直接拖拽到编辑区域直接展示;
  2. 全新的 KaTeX数学公式 语法;
  3. 增加了支持甘特图的mermaid语法1 功能;
  4. 增加了 多屏幕编辑 Markdown文章功能;
  5. 增加了 焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置 等功能,功能按钮位于编辑区域与预览区域中间;
  6. 增加了 检查列表 功能。

功能快捷键

撤销:Ctrl/Command + Z
重做:Ctrl/Command + Y
加粗:Ctrl/Command + B
斜体:Ctrl/Command + I
标题:Ctrl/Command + Shift + H
无序列表:Ctrl/Command + Shift + U
有序列表:Ctrl/Command + Shift + O
检查列表:Ctrl/Command + Shift + C
插入代码:Ctrl/Command + Shift + K
插入链接:Ctrl/Command + Shift + L
插入图片:Ctrl/Command + Shift + G
查找:Ctrl/Command + F
替换:Ctrl/Command + G

合理的创建标题,有助于目录的生成

直接输入1次#,并按下space后,将生成1级标题。
输入2次#,并按下space后,将生成2级标题。
以此类推,我们支持6级标题。有助于使用TOC语法后生成一个完美的目录。

如何改变文本的样式

强调文本 强调文本

加粗文本 加粗文本

标记文本

删除文本

引用文本

H2O is是液体。

210 运算结果是 1024.

插入链接与图片

链接: link.

图片: Alt

带尺寸的图片: Alt

居中的图片: Alt

居中并且带尺寸的图片: Alt

当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。

如何插入一段漂亮的代码片

博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代码片.

// An highlighted block
var foo = 'bar';

生成一个适合你的列表

  • 项目
    • 项目
      • 项目
  1. 项目1
  2. 项目2
  3. 项目3
  • 计划任务
  • 完成任务

创建一个表格

一个简单的表格是这么创建的:

项目 Value
电脑 $1600
手机 $12
导管 $1

设定内容居中、居左、居右

使用:---------:居中
使用:----------居左
使用----------:居右

第一列 第二列 第三列
第一列文本居中 第二列文本居右 第三列文本居左

SmartyPants

SmartyPants 是一个文本转换工具,主要功能是将普通的 ASCII 标点符号自动转换为更美观的印刷体标点符号。例如:

原始符号 转换后 说明
"引号" “引号” 直引号变弯引号
'单引号' ‘单引号’ 直单引号变弯单引号
-- 两个连字符变短破折号
--- 三个连字符变长破折号
... 三个点变省略号

创建一个自定义列表

Markdown
Text-to- HTML conversion tool
Authors
John
Luke

如何创建一个注脚

一个具有注脚的文本。2

注释也是必不可少的

Markdown将文本转换为 HTML

KaTeX数学公式

您可以使用渲染LaTeX数学表达式 KaTeX:

Gamma公式展示 Γ ( n ) = ( n − 1 ) ! ∀ n ∈ N \Gamma(n) = (n-1)!\quad\forall n\in\mathbb N Γ(n)=(n1)!nN 是通过欧拉积分

Γ ( z ) = ∫ 0 ∞ t z − 1 e − t d t   . \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,. Γ(z)=0tz1etdt.

你可以找到更多关于的信息 LaTeX 数学表达式here.

新的甘特图功能,丰富你的文章

2014-01-07 2014-01-09 2014-01-11 2014-01-13 2014-01-15 2014-01-17 2014-01-19 2014-01-21 已完成 进行中 计划一 计划二 现有任务 Adding GANTT diagram functionality to mermaid
  • 关于 甘特图 语法,参考 这儿,

UML图表

可以使用UML图表进行渲染,例如下面产生的一个序列图:

王五 李四 张三 王五 李四 张三 李四想了很长时间, 文字太长了 不适合放在一行. 你好!李四, 最近怎么样? 你最近怎么样,王五? 我很好,谢谢! 我很好,谢谢! 打量着王五... 很好... 王五, 你怎么样?
  • 关于 UML图表 语法,参考 这儿,

流程图

链接

长方形

圆角长方形

菱形

  • 关于 Mermaid 语法,参考 这儿,

FLowchart流程图

我们依旧会支持flowchart.js的流程图语法:

Created with Raphaël 2.3.0 开始 我的操作 确认? 结束 yes no
  • 关于 Flowchart流程图 语法,参考 这儿.

导出与导入

导出

如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。

导入

如果你想加载一篇你写过的.md文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入,
继续你的创作。


  1. mermaid语法说明 ↩︎

  2. 注脚的解释 ↩︎

Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐