diff --git a/.agents/notes/implemented/feature/2026-07-19-human-goal-command.i18n.yaml b/.agents/notes/implemented/feature/2026-07-19-human-goal-command.i18n.yaml index e1a79533b9..78bb900b27 100644 --- a/.agents/notes/implemented/feature/2026-07-19-human-goal-command.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-19-human-goal-command.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -2026-07-19-human-goal-command.md: b195cd6d5d6e50672f2433e2f845e650d825a1f9 -2026-07-19-human-goal-command.zh.md: 84a920c206b875d14349a69da556e945125b53c3 +2026-07-19-human-goal-command.md: f458feae3bed8ef7b0ace6b8baaf5ae8b43e4cc8 +2026-07-19-human-goal-command.zh.md: 2fd79c2f50490e077cc69e93705a7b7bb242a082 diff --git a/.agents/notes/implemented/feature/2026-07-19-human-goal-command.md b/.agents/notes/implemented/feature/2026-07-19-human-goal-command.md index b195cd6d5d..f458feae3b 100644 --- a/.agents/notes/implemented/feature/2026-07-19-human-goal-command.md +++ b/.agents/notes/implemented/feature/2026-07-19-human-goal-command.md @@ -14,7 +14,7 @@ The command must also respect the goal design's two kinds of state. Durable phas `@deepseek-ai/dsh-command-goal` in `packages/goal/command-goal/` is a command producer over `ctx.commands` and `ctx.goals`. It registers one global `goal` definition for the TUI and ACP surfaces. The handler receives the exact target agent from command dispatch, reads or mutates that agent's goal through the domain service, and returns direct plain-text UI output. It does not import either adapter or the concrete agent loop. -The command follows the compact current Codex shape documented by the [official developer-command reference](https://learn.chatgpt.com/docs/developer-commands?surface=cli): bare status, a free-form objective, and `clear`, `edit`, `pause`, or `resume` controls. This repository keeps its own event-sourced state, round-count policy, and post-resume activation rule rather than copying Codex's SQLite, token budget, or automatic-resume behavior. +The command follows the compact Codex shape in the [public OpenAI Codex TUI dispatcher at commit `678157a`](https://github.com/openai/codex/blob/678157acaa819d5510adfe359abb5d0392cfe461/codex-rs/tui/src/chatwidget/slash_dispatch.rs#L750-L805): bare status, a free-form objective, and `clear`, `edit`, `pause`, or `resume` controls. The commit permalink makes the researched grammar durable even as Codex evolves. This repository keeps its own event-sourced state, round-count policy, and post-resume activation rule rather than copying Codex's SQLite, token budget, or automatic-resume behavior. ### Grammar and lifecycle verbs @@ -30,9 +30,9 @@ Control words are ASCII-case-insensitive after outer whitespace trimming. They a ### Output and failure boundary -Status output omits branded ids and compare-and-set revisions because those are model/plugin coordination details rather than human controls. It includes activation because that fact changes whether work will continue. Command hints are derived from the exact state: an armed active goal offers pause, a disarmed active or resumable stopped goal offers resume, budget-limited and completed states do not advertise an invalid resume. +Status output omits branded ids and compare-and-set revisions because those are model/plugin coordination details rather than human controls. It includes activation because that fact changes whether work will continue. Command hints are derived from the exact state: an armed active goal offers pause, a disarmed active or resumable stopped goal offers resume, a budget-limited goal explains that the agent must raise `maxGoalRounds` before resume, and a completed goal offers replacement or clear. -Expected `GoalError` failures become `CommandResult.error`, so invalid human operations receive a stable direct response and never enter model history. Other exceptions remain adapter-visible command failures; treating programmer faults as ordinary domain errors would hide defects. The command handler performs only synchronous domain mutations, so request cancellation is decided by the command registry before the mutation begins and there is no escaped asynchronous side effect to unwind. +Expected `GoalError` failures become one stable, branded-id-free `CommandResult.error`, so domain diagnostics do not leak compare-and-set internals into the human surface and invalid operations never enter model history. The current status supplies the actionable state-specific recovery. Other exceptions remain adapter-visible command failures; treating programmer faults as ordinary domain errors would hide defects. The command handler performs only synchronous domain mutations, so request cancellation is decided by the command registry before the mutation begins and there is no escaped asynchronous side effect to unwind. Generic slash input, status text, and errors are not persisted. Successful goal mutations use the existing `Agent.inject()` path, producing the raw model-visible goal snapshot or clear tombstone that persistence already owns. The command therefore changes no session format and introduces no second audit record that could disagree with the domain event. @@ -40,11 +40,11 @@ Generic slash input, status text, and errors are not persisted. Successful goal `agent-spine-demo` accepts an optional `goals` composition object containing the goal-domain and model-tool owner configs. Omission or `false` leaves the stack unmounted. This explicit opt-in is important for headless one-shot callers: their result API settles one correlated physical turn and must not silently become a long-running logical goal operation. -The terminal and ACP app bundles make the opposite product choice. They default `goals` to the owner defaults, mount the goal domain, model tools, same-session driver, command registry, and this producer, and accept `goals: false` as one coherent opt-out. The TUI and ACP bridge then discover the same definition through the generic registry. The line-oriented stdio mode does not consume the command plane; a typed `/goal` there remains an ordinary human prompt that the model may interpret through its separately authorized goal tools. +The interactive app bundles make the opposite product choice. ACP defaults `goals` to the owner defaults and mounts the goal domain, model tools, same-session driver, command registry, and this producer. The terminal app enables the same goal stack by default but mounts the producer only for TUI mode; line-oriented stdio does not consume the command plane, so a typed `/goal` there remains an ordinary human prompt that the model may interpret through its separately authorized goal tools. Both apps accept `goals: false` as one coherent stack opt-out. The Python SDK runtime closure ships this producer alongside ACP, commands, and the goal stack so an external `cordis.yml` can compose the same command surface. ## Testing -The producer suite uses the real command registry, goal service, agent registry, and session log. It covers Loader-safe exports, discovery on both surfaces, disposal, empty status, objective parsing, unfinished replacement refusal, inline edit, completed replacement, all missing-state controls, pause/resume/clear, every durable phase, armed/disarmed presentation, round-budget presentation, expected domain errors, unexpected failures, and persisted mutation records. App composition tests cover explicit spine opt-in, terminal/ACP defaults, coherent opt-out, forwarded domain/tool config, command discovery, and the expanded model-tool assembly. The keyless ACP snapshots pin the resulting `/goal` discovery metadata and goal tool schemas in the shipped app composition. +The producer suite uses the real command registry, goal service, agent registry, and session log. It covers Loader-safe exports, discovery on both surfaces, disposal, empty status, objective parsing, unfinished replacement refusal, inline edit, completed replacement, all missing-state controls, pause/resume/clear, every durable phase, armed/disarmed presentation, budget-exhaustion recovery guidance, sanitized domain errors, unexpected failures, and persisted mutation records. App composition tests cover explicit spine opt-in, TUI/ACP defaults, readline producer absence, coherent opt-out, forwarded domain/tool config, command discovery, the packaged-runtime closure, and the expanded model-tool assembly. The keyless ACP snapshots pin the resulting `/goal` discovery metadata and goal tool schemas in the shipped app composition. ## Alternatives considered diff --git a/.agents/notes/implemented/feature/2026-07-19-human-goal-command.zh.md b/.agents/notes/implemented/feature/2026-07-19-human-goal-command.zh.md index 84a920c206..2fd79c2f50 100644 --- a/.agents/notes/implemented/feature/2026-07-19-human-goal-command.zh.md +++ b/.agents/notes/implemented/feature/2026-07-19-human-goal-command.zh.md @@ -14,7 +14,7 @@ Status: implemented 位于 `packages/goal/command-goal/` 的 `@deepseek-ai/dsh-command-goal` 是构建在 `ctx.commands` 与 `ctx.goals` 之上的命令生产方。它为 TUI 和 ACP 表面注册一个全局 `goal` 定义。处理器从命令分发接收准确的目标 agent(智能体),通过领域服务读取或改变该 agent 的目标,并返回直接的纯文本 UI 输出。它不导入任何适配器或具体 agent loop(智能体循环)。 -该命令遵循[官方开发者命令参考](https://learn.chatgpt.com/docs/developer-commands?surface=cli)所记录的当前 Codex 紧凑形态:无参数状态查询、自由形式目标描述,以及 `clear`、`edit`、`pause` 或 `resume` 控制。本仓库保留自身的事件溯源状态、回合计数策略与恢复后激活规则,而不复制 Codex 的 SQLite、token 预算或自动恢复行为。 +该命令遵循 [OpenAI Codex 公共仓库 `678157a` 提交中的 TUI 分发实现](https://github.com/openai/codex/blob/678157acaa819d5510adfe359abb5d0392cfe461/codex-rs/tui/src/chatwidget/slash_dispatch.rs#L750-L805)所呈现的紧凑形态:无参数状态查询、自由形式目标描述,以及 `clear`、`edit`、`pause` 或 `resume` 控制。固定到提交的链接使调研所得语法在 Codex 后续演进时仍可核验。本仓库保留自身的事件溯源状态、回合计数策略与恢复后激活规则,而不复制 Codex 的 SQLite、token 预算或自动恢复行为。 ### 语法与生命周期动词 @@ -30,9 +30,9 @@ Status: implemented ### 输出与失败边界 -状态输出省略品牌化 id 与比较并交换修订号,因为它们属于模型/插件协调细节,而不是人类控制项。输出包含激活态,因为该事实会改变工作是否继续。命令提示从准确状态派生:已激活的活跃目标提供暂停,未激活的活跃目标或可恢复的停止目标提供恢复,受预算限制和已完成状态不会宣传非法的恢复操作。 +状态输出省略品牌化 id 与比较并交换修订号,因为它们属于模型/插件协调细节,而不是人类控制项。输出包含激活态,因为该事实会改变工作是否继续。命令提示从准确状态派生:已激活的活跃目标提供暂停,未激活的活跃目标或可恢复的停止目标提供恢复,受预算限制的目标说明 agent 必须先提高 `maxGoalRounds` 才能恢复,已完成目标则提供替换或清除。 -预期的 `GoalError` 失败会变为 `CommandResult.error`,因此非法人类操作会收到稳定的直接响应,且绝不会进入模型历史。其他异常仍是适配器可见的命令失败;若把程序缺陷当成普通领域错误,就会隐藏问题。命令处理器只执行同步领域变更,因此请求取消会在变更开始前由命令注册表决定,不存在需要回滚的外逸异步副作用。 +预期的 `GoalError` 失败会变为一个稳定且不含品牌化 id 的 `CommandResult.error`,使领域诊断不会向人类表面泄露比较并交换内部细节,非法操作也绝不会进入模型历史。当前状态负责提供针对具体状态且可执行的恢复路径。其他异常仍是适配器可见的命令失败;若把程序缺陷当成普通领域错误,就会隐藏问题。命令处理器只执行同步领域变更,因此请求取消会在变更开始前由命令注册表决定,不存在需要回滚的外逸异步副作用。 通用斜杠输入、状态文本与错误不会持久化。成功的目标变更使用现有 `Agent.inject()` 路径,产出持久化本就拥有的原始模型可见目标快照或清除墓碑。因此该命令不会改变会话格式,也不会引入可能与领域事件不一致的第二份审计记录。 @@ -40,11 +40,11 @@ Status: implemented `agent-spine-demo` 接受可选的 `goals` 组合对象,其中包含目标领域与模型工具的所有者配置。省略或设为 `false` 时不会挂载该栈。对无头单次调用方而言,明确选择加入非常重要:它们的结果 API 会在一个相关物理轮次后结束,不能静默变成长时间运行的逻辑目标操作。 -终端和 ACP 应用包作出相反的产品选择。它们默认让 `goals` 使用所有者默认值,挂载目标领域、模型工具、同会话驱动器、命令注册表与本生产方,并接受 `goals: false` 作为一致的整体退出选项。随后,TUI 与 ACP 桥通过通用注册表发现同一个定义。行式 stdio 模式不消费命令平面;在那里输入的 `/goal` 仍是普通人类提示词,模型可以通过独立授权的目标工具解释它。 +交互式应用包作出相反的产品选择。ACP 默认让 `goals` 使用所有者默认值,并挂载目标领域、模型工具、同会话驱动器、命令注册表与本生产方。终端应用默认启用相同目标栈,但只在 TUI 模式挂载本生产方;行式 stdio 不消费命令平面,因此在那里输入的 `/goal` 仍是普通人类提示词,模型可以通过独立授权的目标工具解释它。两个应用都接受 `goals: false` 作为一致的整体退出选项。Python SDK 运行时闭包把本生产方与 ACP、命令及目标栈一并交付,使外部 `cordis.yml` 能组合相同命令表面。 ## 测试 -生产方测试套件使用真实命令注册表、目标服务、agent 注册表与会话日志。它覆盖 Loader 安全导出、两个表面的发现、资源释放、空状态、目标描述解析、拒绝未完成目标替换、行内编辑、已完成目标替换、所有缺失状态控制、暂停/恢复/清除、每个持久阶段、已激活/未激活展示、回合预算展示、预期领域错误、意外失败与持久变更记录。应用组合测试覆盖显式主干选择加入、终端/ACP 默认值、一致退出、转发的领域/工具配置、命令发现与扩展后的模型工具组装。无密钥 ACP 快照固定了交付应用组合中的 `/goal` 发现元数据和目标工具 schema。 +生产方测试套件使用真实命令注册表、目标服务、agent 注册表与会话日志。它覆盖 Loader 安全导出、两个表面的发现、资源释放、空状态、目标描述解析、拒绝未完成目标替换、行内编辑、已完成目标替换、所有缺失状态控制、暂停/恢复/清除、每个持久阶段、已激活/未激活展示、预算耗尽恢复提示、经净化的领域错误、意外失败与持久变更记录。应用组合测试覆盖显式主干选择加入、TUI/ACP 默认值、readline 不挂载生产方、一致退出、转发的领域/工具配置、命令发现、打包运行时闭包与扩展后的模型工具组装。无密钥 ACP 快照固定了交付应用组合中的 `/goal` 发现元数据和目标工具 schema。 ## 考虑过的替代方案 diff --git a/docs/config-catalog.md b/docs/config-catalog.md index 0087f417dd..a8840b2bca 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -891,7 +891,7 @@ export interface Config { toolBash?: NonNullable /** Generic background-task controls forwarded through agent-core; set false to omit their tool surface. */ toolTasks?: NonNullable - /** Persisted same-session goals; owner defaults enable them, or false disables the stack and command. */ + /** Persisted same-session goals; owner defaults enable them, or false disables the stack and TUI command. */ goals?: agentCore.GoalConfig | false /** * If set, the pre-created agent RESUMES this persisted session id instead of diff --git a/packages/examples/stdio-demo/README.md b/packages/examples/stdio-demo/README.md index 4f452aa6d9..0f275485b0 100644 --- a/packages/examples/stdio-demo/README.md +++ b/packages/examples/stdio-demo/README.md @@ -12,7 +12,7 @@ A terminal chat always wants the same cluster, so the package owns it rather tha |---|---| | `@deepseek-ai/dsh-agent-spine-demo` | the spine, pre-creating a `main` agent from this app's provider/model pair with `process.cwd()` as the fresh session cwd and carrying its `persona` | | `@deepseek-ai/dsh-commands` | the human-command registry consumed by the TUI front door and optional command plugins | -| `@deepseek-ai/dsh-command-goal` | the direct `/goal` producer; the app enables the spine's persisted-goal stack with it | +| `@deepseek-ai/dsh-command-goal` | the direct `/goal` producer mounted only for the TUI front door; readline retains the model-mediated goal path | | `@deepseek-ai/dsh-session-persistence-jsonl` | durable JSONL session log under `persistenceRoot` | | `@deepseek-ai/dsh-user-interaction` | the human question/answer seam used by confirmation tools | | `@deepseek-ai/dsh-tool-ask-user` | the model-facing `ask_user_question` tool | @@ -38,7 +38,7 @@ The leaf `cordis.yml` supplies only the **swappable backends** — an LLM adapte | `skills` | owner defaults | registry-cache, local-provider, and model-facing skill-tool config, routed through `dsh-agent-spine-demo` | | `toolBash` | owner defaults | model-facing bash config routed through `dsh-agent-spine-demo`, including bash's producer-local `enableRunInBackground` | | `toolTasks` | owner defaults | generic `task_output` wait bounds routed through `dsh-agent-spine-demo` | -| `goals` | owner defaults | persisted goal-domain and model-tool config; `false` removes the goal stack and `/goal` producer | +| `goals` | owner defaults | persisted goal-domain and model-tool config; `false` removes the goal stack and the TUI `/goal` producer | | `persistenceRoot` | `./.sessions` | the JSONL backend's root directory | | `welcome` | `ready.` | terminal banner / TUI subtitle | | `ui` | `{ mode: 'auto' }` | terminal mode (`auto` / `readline` / `tui`) and nested TUI presentation config | diff --git a/packages/examples/stdio-demo/src/index.ts b/packages/examples/stdio-demo/src/index.ts index 307a0db483..5e399a56ca 100644 --- a/packages/examples/stdio-demo/src/index.ts +++ b/packages/examples/stdio-demo/src/index.ts @@ -101,7 +101,7 @@ export interface Config { toolBash?: NonNullable /** Generic background-task controls forwarded through agent-core; set false to omit their tool surface. */ toolTasks?: NonNullable - /** Persisted same-session goals; owner defaults enable them, or false disables the stack and command. */ + /** Persisted same-session goals; owner defaults enable them, or false disables the stack and TUI command. */ goals?: agentCore.GoalConfig | false /** * If set, the pre-created agent RESUMES this persisted session id instead of @@ -152,7 +152,7 @@ export function composeTerminalApp(ctx: Context, config: Config, isTTY: boolean) const goals = config.goals ?? {} if (mode === 'readline') ctx.plugin(ConsoleExporter) ctx.plugin(CommandService) - if (goals !== false) ctx.plugin(commandGoal) + if (mode === 'tui' && goals !== false) ctx.plugin(commandGoal) ctx.plugin(SessionPersistenceJsonl, { root: config.persistenceRoot ?? DEFAULT_PERSISTENCE_ROOT }) ctx.plugin(UserInteractionService) if (mode === 'tui') { diff --git a/packages/examples/stdio-demo/tests/stdio-agent.spec.ts b/packages/examples/stdio-demo/tests/stdio-agent.spec.ts index f20860f1eb..2480cb704d 100644 --- a/packages/examples/stdio-demo/tests/stdio-agent.spec.ts +++ b/packages/examples/stdio-demo/tests/stdio-agent.spec.ts @@ -118,17 +118,25 @@ describe('dsh-stdio-demo app', () => { calls.length = 0 stdioAgent.composeTerminalApp(ctx, { - provider: 'mock', model: 'mock', workspaceContext: false, goals: false, ui: { mode: 'readline' }, + provider: 'mock', model: 'mock', workspaceContext: false, goals: false, ui: { mode: 'tui' }, + }, true) + expect(calls.map(call => call.name)).toContain('ui-tui') + expect(calls.map(call => call.name)).not.toContain('command-goal') + expect(calls.find(call => call.name === 'agent-spine-demo')?.config).toMatchObject({ goals: false }) + + calls.length = 0 + stdioAgent.composeTerminalApp(ctx, { + provider: 'mock', model: 'mock', workspaceContext: false, ui: { mode: 'readline' }, }, false) expect(calls.map(call => call.name)).toContain('ui-stdio') expect(calls.map(call => call.name)).toContain('ConsoleExporter') expect(calls.map(call => call.name)).not.toContain('ui-tui') expect(calls.map(call => call.name)).not.toContain('command-goal') - expect(calls.find(call => call.name === 'agent-spine-demo')?.config).toMatchObject({ goals: false }) + expect(calls.find(call => call.name === 'agent-spine-demo')?.config).toMatchObject({ goals: {} }) }) it('composes the spine + front-door cluster and pre-creates the main agent', async () => { - const ctx = await mount({ provider: 'mock', model: 'mock', persona: 'hi', persistenceRoot: '/tmp/dsh-stdio-demo-spec', skills: await isolatedSkillsConfig(), workspaceContext: false }) + const ctx = await mount({ provider: 'mock', model: 'mock', persona: 'hi', persistenceRoot: '/tmp/dsh-stdio-demo-spec', skills: await isolatedSkillsConfig(), workspaceContext: false, ui: { mode: 'readline' } }) // The spine services (brought up by the agent-spine-demo bundle) are all present. expect(ctx.get('agents')).toBeDefined() expect(ctx.get('agentLoop')).toBeDefined() @@ -145,7 +153,7 @@ describe('dsh-stdio-demo app', () => { expect(agent?.id).toBe(agent?.session.id) expect(agent?.id).toMatch(/^main-session-/) expect(agent?.session.header.cwd).toBe(process.cwd()) - expect(ctx.commands.find(agent!, 'tui', 'goal')).toBeDefined() + expect(ctx.commands.find(agent!, 'tui', 'goal')).toBeUndefined() await ctx.fiber.dispose() }) diff --git a/packages/goal/command-goal/README.md b/packages/goal/command-goal/README.md index c971297cd3..2fa7323789 100644 --- a/packages/goal/command-goal/README.md +++ b/packages/goal/command-goal/README.md @@ -15,7 +15,7 @@ Human-facing `/goal` control over [`ctx.goals`](../goal/README.md). The plugin r Control words are case-insensitive only when they occupy the complete input. Every other non-empty suffix is an objective, so `/goal pause after verification` creates that literal objective. The goal domain trims and validates objectives. Because the generic command plane has no modal editor or confirmation primitive, `edit` takes its replacement inline and an unfinished replacement returns a direct error instructing the user to edit or clear. -Expected domain rejections become direct command errors. Unexpected implementation failures still reject dispatch so adapters can report them as command failures. Generic command text and output remain live UI state; every accepted mutation is persisted and made model-visible by `dsh-goal` rather than by this plugin. +Expected domain rejections become stable direct command errors without exposing branded ids or revisions. Unexpected implementation failures still reject dispatch so adapters can report them as command failures. Generic command text and output remain live UI state; every accepted mutation is persisted and made model-visible by `dsh-goal` rather than by this plugin. ## Composition @@ -30,7 +30,7 @@ The producer injects `commands` and `goals`. A custom app mounts their owners pl name: '@deepseek-ai/dsh-command-goal' ``` -The terminal and ACP demo apps enable the complete persisted-goal stack and this command by default; `goals: false` removes both. The UI-less `agent-spine-demo` requires an explicit `goals: {}` so headless one-shot callers do not silently change from one physical turn to a multi-round operation. +The TUI and ACP demo apps enable the complete persisted-goal stack and this command by default; `goals: false` removes both. The terminal app's readline mode keeps the model-mediated goal stack but does not mount this producer because that front door does not consume commands. The UI-less `agent-spine-demo` requires an explicit `goals: {}` so headless one-shot callers do not silently change from one physical turn to a multi-round operation. ## Model Experience diff --git a/packages/goal/command-goal/src/index.ts b/packages/goal/command-goal/src/index.ts index 3fff035adc..a8e73cba7e 100644 --- a/packages/goal/command-goal/src/index.ts +++ b/packages/goal/command-goal/src/index.ts @@ -69,7 +69,7 @@ function commandHint(goal: GoalView): string { case 'usage-limited': return '/goal edit , /goal resume, /goal clear' case 'budget-limited': - return '/goal edit , /goal clear' + return '/goal edit , /goal clear; after the agent raises the round cap, /goal resume' case 'complete': return '/goal , /goal clear' /* v8 ignore next 2 -- the active branch and every non-active phase are handled above */ @@ -149,7 +149,12 @@ function executeGoalCommand(ctx: Context, invocation: CommandInvocation): Comman default: return assertNever(command, 'goal command') } } catch (error: unknown) { - if (error instanceof GoalError) return { kind: 'error', text: error.message } + if (error instanceof GoalError) { + return { + kind: 'error', + text: 'The goal command is not valid for the current state. Run /goal to view available commands.', + } + } throw error } } diff --git a/packages/goal/command-goal/tests/command-goal.spec.ts b/packages/goal/command-goal/tests/command-goal.spec.ts index e13f5e9822..a3d48857c4 100644 --- a/packages/goal/command-goal/tests/command-goal.spec.ts +++ b/packages/goal/command-goal/tests/command-goal.spec.ts @@ -197,8 +197,10 @@ describe('/goal human command', () => { const test = await harness() await run(test, ' work') const redundantResume = await run(test, ' RESUME') - expect(redundantResume.kind).toBe('error') - expect(redundantResume.text).toContain('already active and armed') + expect(redundantResume).toEqual({ + kind: 'error', + text: 'The goal command is not valid for the current state. Run /goal to view available commands.', + }) const paused = await run(test, ' PAUSE') expect(paused.kind).toBe('success') expect(paused.text).toContain('Goal paused') @@ -238,7 +240,7 @@ describe('/goal human command', () => { goal = test.ctx.goals.markBudgetLimited(test.agent, ref(goal)) const limited = await run(test) expect(limited.text).toContain('Status: limited by round budget') - expect(limited.text).not.toContain('/goal resume') + expect(limited.text).toContain('after the agent raises the round cap, /goal resume') goal = test.ctx.goals.complete(test.agent, ref(goal)) const complete = await run(test) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1a45c8b505..9ff42e490e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2656,6 +2656,9 @@ importers: '@deepseek-ai/dsh-code-runtime-worker': specifier: workspace:^ version: link:../../packages/code-runtime/code-runtime-worker + '@deepseek-ai/dsh-command-goal': + specifier: workspace:^ + version: link:../../packages/goal/command-goal '@deepseek-ai/dsh-commands': specifier: workspace:^ version: link:../../packages/ui/commands diff --git a/python/sdk-runtime/package.json b/python/sdk-runtime/package.json index e49d7a5a03..d189399e21 100644 --- a/python/sdk-runtime/package.json +++ b/python/sdk-runtime/package.json @@ -18,6 +18,7 @@ "@deepseek-ai/dsh-brand": "workspace:^", "@deepseek-ai/dsh-code-runtime": "workspace:^", "@deepseek-ai/dsh-code-runtime-worker": "workspace:^", + "@deepseek-ai/dsh-command-goal": "workspace:^", "@deepseek-ai/dsh-commands": "workspace:^", "@deepseek-ai/dsh-compact": "workspace:^", "@deepseek-ai/dsh-compact-basic": "workspace:^",