refactor(plan): remove generic mode abstraction
This commit is contained in:
@@ -30,4 +30,4 @@ An agent demo exposed as an **Agent Client Protocol (ACP)** server over JSON-RPC
|
||||
|
||||
Run with: `pnpm run demo:acp` (needs `DEEPSEEK_API_KEY`); `pnpm run demo:code-mode acp` boots the same server in Code Mode via the `code-mode.cordis.yml` overlay. See [acp-agent/README.md](acp-agent/README.md) for the Zed setup and the snapshot-test design.
|
||||
|
||||
The default `cordis.yml` composes [`@deepseek-ai/dsh-mode`](../packages/mode/mode), [`@deepseek-ai/dsh-sandbox-local`](../packages/sandbox/sandbox-local), [`@deepseek-ai/dsh-bash-sandbox`](../packages/bash/bash-sandbox), [`@deepseek-ai/dsh-user-approval`](../packages/ui/user-approval), and [`@deepseek-ai/dsh-permission`](../packages/ui/permission). A capable client gets a `default` / `plan` mode picker plus one independent `Permissions` select: plan adds model guidance and the reviewed `exit_plan_mode` crossing without changing enforcement, while `workspace-write` confines bash to the configured workspace and asks before a wider retry. See [acp-agent/README.md](acp-agent/README.md#plan-mode) for the plan-review and elicitation flow.
|
||||
The default `cordis.yml` composes [`@deepseek-ai/dsh-plan-mode`](../packages/plan/plan-mode), [`@deepseek-ai/dsh-sandbox-local`](../packages/sandbox/sandbox-local), [`@deepseek-ai/dsh-bash-sandbox`](../packages/bash/bash-sandbox), [`@deepseek-ai/dsh-user-approval`](../packages/ui/user-approval), and [`@deepseek-ai/dsh-permission`](../packages/ui/permission). A capable client gets a `default` / `plan` mode picker plus one independent `Permissions` select: plan adds model guidance and the reviewed `exit_plan_mode` crossing without changing enforcement, while `workspace-write` confines bash to the configured workspace and asks before a wider retry. See [acp-agent/README.md](acp-agent/README.md#plan-mode) for the plan-review and elicitation flow.
|
||||
|
||||
@@ -7,7 +7,7 @@ pnpm run demo:acp # needs DEEPSEEK_API_KEY (repo-root .env or env)
|
||||
pnpm run demo:code-mode acp # the same server in Code Mode: one wire tool, run_code
|
||||
```
|
||||
|
||||
The leaf config loads the ACP app, DeepSeek adapter, session modes, sandboxed bash, the sandboxed filesystem stack, approval and permission services, model-facing tools, and repeat guard. The app bundles the agent spine, JSONL persistence, and bridge, creates agents on `session/new`, and keeps stdout logger-free. [`fs.cordis.yml`](fs.cordis.yml) adds local tool-result spill storage for its dedicated scenarios; [`code-mode.cordis.yml`](code-mode.cordis.yml) adds `run_code` and its generated TypeScript SDK. See [Code Mode](../../packages/core/tools/README.md#code-mode).
|
||||
The leaf config loads the ACP app, DeepSeek adapter, plan mode, sandboxed bash, the sandboxed filesystem stack, approval and permission services, model-facing tools, and repeat guard. The app bundles the agent spine, JSONL persistence, and bridge, creates agents on `session/new`, and keeps stdout logger-free. [`fs.cordis.yml`](fs.cordis.yml) adds local tool-result spill storage for its dedicated scenarios; [`code-mode.cordis.yml`](code-mode.cordis.yml) adds `run_code` and its generated TypeScript SDK. See [Code Mode](../../packages/core/tools/README.md#code-mode).
|
||||
|
||||
## stdout is the protocol
|
||||
|
||||
@@ -29,13 +29,13 @@ Add to your Zed `settings.json` under `agent_servers`:
|
||||
}
|
||||
```
|
||||
|
||||
The editor sets each session's `cwd` to the project it opens, and bash uses that directory as its workdir. The current sandbox write boundary is nevertheless fixed when the server starts (`workspaceRoot: process.cwd()`), so launch the server from the workspace it should be allowed to modify; making that root session-scoped is deferred in the [sandbox Agent Note](../../.agents/notes/implemented/feature/2026-07-06-sandbox.md). The filesystem tools now ride the same sandbox policy through [`@deepseek-ai/dsh-fs-sandbox`](../../packages/fs/fs-sandbox/), so `read`/`write`/`edit` are available under every mode and confined to the same `workspaceRoot`.
|
||||
The editor sets each session's `cwd` to the project it opens, and bash uses that directory as its workdir. The current sandbox write boundary is nevertheless fixed when the server starts (`workspaceRoot: process.cwd()`), so launch the server from the workspace it should be allowed to modify; making that root session-scoped is deferred in the [sandbox Agent Note](../../.agents/notes/implemented/feature/2026-07-06-sandbox.md). The filesystem tools ride the same sandbox policy through [`@deepseek-ai/dsh-fs-sandbox`](../../packages/fs/fs-sandbox/), so `read`/`write`/`edit` remain available regardless of plan state and confined to the same `workspaceRoot`.
|
||||
|
||||
## Plan mode
|
||||
|
||||
The same `demo:acp` server composes [`@deepseek-ai/dsh-mode`](../../packages/mode/mode/), so a capable client advertises `default` and `plan` in its mode picker. This composition owns the complete plan instructions in [`cordis.yml`](cordis.yml): remain in the selected mode, inspect before asking, avoid mutations, resolve discoverable repository facts, and submit a decision-complete plan through `exit_plan_mode`. Those are the instrumental behaviors shared by the local Codex and Claude Code references; product-specific plan files, phase machinery, and protocol tags stay out of the plugin contract.
|
||||
The same `demo:acp` server composes [`@deepseek-ai/dsh-plan-mode`](../../packages/plan/plan-mode/), so a capable client advertises `default` and `plan` in its mode picker. ACP owns those protocol ids and projects them onto the plugin's boolean plan state. This composition owns the complete plan instructions in [`cordis.yml`](cordis.yml): remain in plan mode, inspect before asking, avoid mutations, resolve discoverable repository facts, and submit a decision-complete plan through `exit_plan_mode`. Those are the instrumental behaviors shared by the local Codex and Claude Code references; product-specific plan files, phase machinery, and protocol tags stay out of the plugin contract.
|
||||
|
||||
Plan mode adds only that configured guidance section. Every tool, including `exit_plan_mode`, keeps the same schema in `default` and `plan`; the exit tool describes itself as plan-only and rejects if called outside plan mode. Stable native schemas and Code Mode SDK bindings avoid tool-catalog churn at the transition. `ask_user_question` carries blocking user-owned choices through ACP elicitation, while `exit_plan_mode` renders the exact logged plan for approval and returns keep-planning feedback to the model. The mode picker and permission select remain independent: switching modes never changes sandbox or approval state, and deployments that need a hard read-only planning floor configure that policy separately. The [plan-mode Agent Note](../../.agents/notes/implemented/feature/2026-07-07-plan-mode.md) owns the state and review contract.
|
||||
Plan mode adds only that configured guidance section. Every tool, including `exit_plan_mode`, keeps the same schema while plan mode is inactive or active; the exit tool describes itself as plan-only and rejects if called while inactive. Stable native schemas and Code Mode SDK bindings avoid tool-catalog churn at the transition. `ask_user_question` carries blocking user-owned choices through ACP elicitation, while `exit_plan_mode` renders the exact logged plan for approval and returns keep-planning feedback to the model. The mode picker and permission select remain independent: switching plan state never changes sandbox or approval state, and deployments that need a hard read-only planning floor configure that policy separately. The [plan-mode Agent Note](../../.agents/notes/implemented/feature/2026-07-07-plan-mode.md) owns the state and review contract.
|
||||
|
||||
## Snapshot tests (record-once / replay-deterministic)
|
||||
|
||||
|
||||
@@ -29,8 +29,8 @@ flowchart LR
|
||||
bundle_agent_core --> spine_sessions["ctx.sessions"]
|
||||
bundle_agent_core --> spine_tools["ctx.tools + tool-bash"]
|
||||
bundle_agent_core --> spine_loop["ctx.agents + ctx.agentLoop"]
|
||||
plugin_acp_mode["mode<br/>@deepseek-ai/dsh-mode"]
|
||||
cfg --> plugin_acp_mode
|
||||
plugin_acp_plan_mode["plan-mode<br/>@deepseek-ai/dsh-plan-mode"]
|
||||
cfg --> plugin_acp_plan_mode
|
||||
plugin_acp_tool_ask_user["tool-ask-user<br/>@deepseek-ai/dsh-tool-ask-user"]
|
||||
cfg --> plugin_acp_tool_ask_user
|
||||
plugin_acp_token_meter["token-meter<br/>@deepseek-ai/dsh-token-meter"]
|
||||
@@ -78,7 +78,7 @@ flowchart LR
|
||||
| `approval` | `@deepseek-ai/dsh-user-approval` |
|
||||
| `permission` | `@deepseek-ai/dsh-permission` |
|
||||
| `acp-agent` | `@deepseek-ai/dsh-acp-demo` |
|
||||
| `mode` | `@deepseek-ai/dsh-mode` |
|
||||
| `plan-mode` | `@deepseek-ai/dsh-plan-mode` |
|
||||
| `tool-ask-user` | `@deepseek-ai/dsh-tool-ask-user` |
|
||||
| `token-meter` | `@deepseek-ai/dsh-token-meter` |
|
||||
| `compact-basic` | `@deepseek-ai/dsh-compact-basic` |
|
||||
|
||||
@@ -63,15 +63,12 @@
|
||||
|
||||
Verify your work by running the code or tests. Keep answers brief and factual.
|
||||
|
||||
# Collaboration modes are additive to the canonical ACP server. The ACP bridge
|
||||
# advertises the picker, while dsh-mode owns the logged state, plan guidance,
|
||||
# and reviewed exit; sandbox and approval remain independent session options.
|
||||
- id: mode
|
||||
name: '@deepseek-ai/dsh-mode'
|
||||
# Plan mode is additive to the canonical ACP server. The ACP bridge projects
|
||||
# it onto the protocol picker; sandbox and approval remain independent options.
|
||||
- id: plan-mode
|
||||
name: '@deepseek-ai/dsh-plan-mode'
|
||||
config:
|
||||
modes:
|
||||
plan:
|
||||
section: |
|
||||
section: |
|
||||
You are in plan mode. Stay in plan mode until exit_plan_mode succeeds or the user switches the session mode. Imperative language to implement changes means plan the implementation, not execute it. A user's conversational agreement — including an answer confirming something you asked — approves nothing and does not end plan mode; fold the confirmed decision into the plan and submit it through exit_plan_mode.
|
||||
|
||||
Explore first. Use non-mutating reads, searches, static analysis, and checks to ground the plan in the actual repository. Do not edit or write files, change configuration, run formatters or code generation that rewrites tracked files, commit, or otherwise carry out the plan. Prefer existing functions and patterns over new machinery.
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
{"jsonrpc":"2.0","id":3,"result":{}}
|
||||
{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"{{sessionId}}","update":{"sessionUpdate":"current_mode_update","currentModeId":"default"}}}
|
||||
{"jsonrpc":"2.0","id":4,"result":{}}
|
||||
{"jsonrpc":"2.0","id":5,"error":{"code":-32602,"message":"Invalid params: unknown mode \"yolo\" — available modes: default, plan"}}
|
||||
{"jsonrpc":"2.0","id":5,"error":{"code":-32602,"message":"Invalid params: unknown session mode \"yolo\" — available modes: default, plan"}}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{"type":"session","version":0,"id":"50138298-1385-449e-b252-146acb0571d0","createdAt":1784525384931,"cwd":"/var/folders/2g/b32ct0qn1d728l_v6tdkjytr0000gn/T/acp-snap-cwd-DTr6Ra","delegationDepth":0}
|
||||
{"type":"turn/start","seq":0,"time":1784525384935,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user"}}}}
|
||||
{"type":"mode/set","seq":1,"time":1784525384935,"data":{"mode":"plan"}}
|
||||
{"type":"plan/mode","seq":1,"time":1784525384935,"data":{"active":true}}
|
||||
{"type":"user/message","seq":2,"time":1784525384935,"data":{"content":[{"type":"text","text":"Read the file notes.txt (use the relative path notes.txt exactly, never an absolute path), then present a short plan titled '# Fix the greeting typo' via exit_plan_mode, exactly once. If the review does not approve, summarize the reviewer's feedback in plain text and end your reply - do not present the plan again."}],"source":{"kind":"user"}},"surfaceOp":"append"}
|
||||
{"type":"session/title","seq":3,"time":1784525384935,"data":{"title":"Read the file notes.txt (use","messageSeqs":[2],"source":{"kind":"fallback"}}}
|
||||
{"type":"step/start","seq":4,"time":1784525384938,"data":{"turn":1,"step":1}}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{"type":"session","version":0,"id":"5fa52100-2da3-4be1-ac1d-6c2e1fba01c6","createdAt":1784525374896,"cwd":"/var/folders/2g/b32ct0qn1d728l_v6tdkjytr0000gn/T/acp-snap-cwd-tia05W","delegationDepth":0}
|
||||
{"type":"turn/start","seq":0,"time":1784525374902,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user"}}}}
|
||||
{"type":"mode/set","seq":1,"time":1784525374902,"data":{"mode":"plan"}}
|
||||
{"type":"plan/mode","seq":1,"time":1784525374902,"data":{"active":true}}
|
||||
{"type":"user/message","seq":2,"time":1784525374902,"data":{"content":[{"type":"text","text":"The typo is on line 2 of notes.txt itself; the workspace contains no other file. Inspect it with the bash tool: run exactly `cat notes.txt` (relative path, no other command). Then present a one-step plan titled '# Fix the greeting typo' via exit_plan_mode: the single step is editing line 2 of notes.txt to say hello world. Do not edit anything before the review approves. After it approves, apply exactly that one edit to notes.txt with the edit tool and stop — no other commands, do not look for any other file."}],"source":{"kind":"user"}},"surfaceOp":"append"}
|
||||
{"type":"session/title","seq":3,"time":1784525374902,"data":{"title":"The typo is on line","messageSeqs":[2],"source":{"kind":"fallback"}}}
|
||||
{"type":"step/start","seq":4,"time":1784525374904,"data":{"turn":1,"step":1}}
|
||||
@@ -228,7 +228,7 @@
|
||||
{"type":"tool/call","seq":226,"time":1784525378322,"data":{"turn":1,"step":2,"callId":"call_00_6RLxuiGtAFswvfLnWdt63143","name":"exit_plan_mode","arguments":"{\"plan\": \"# Fix the greeting typo\\n\\n## Single step\\n1. **Edit line 2 of `notes.txt`** — Replace the content of line 2 (`- the greeting message still says \\\"helo wrld\\\"`) with `hello world`.\"}"}}
|
||||
{"type":"tool/result","seq":227,"time":1784525378329,"data":{"turn":1,"step":2,"callId":"call_00_6RLxuiGtAFswvfLnWdt63143","content":[{"type":"text","text":"Plan approved — plan mode exited; carry out the plan starting with your next step."}],"isError":false},"sourceEventSeqs":[226],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":228,"time":1784525378329,"data":{"turn":1,"step":2}}
|
||||
{"type":"mode/set","seq":229,"time":1784525378329,"data":{"mode":"default"}}
|
||||
{"type":"plan/mode","seq":229,"time":1784525378329,"data":{"active":false}}
|
||||
{"type":"step/start","seq":230,"time":1784525378330,"data":{"turn":1,"step":3}}
|
||||
{"type":"request/header","seq":231,"time":1784553020470,"data":{"header":{"config":{"provider":"deepseek","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"change"}}
|
||||
{"type":"assistant/chunk","seq":232,"time":1784525378723,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"@deepseek-ai/dsh-llm-replay": "workspace:*",
|
||||
"@deepseek-ai/dsh-lsp": "workspace:*",
|
||||
"@deepseek-ai/dsh-lsp-local": "workspace:*",
|
||||
"@deepseek-ai/dsh-mode": "workspace:*",
|
||||
"@deepseek-ai/dsh-plan-mode": "workspace:*",
|
||||
"@deepseek-ai/dsh-permission": "workspace:*",
|
||||
"@deepseek-ai/dsh-repeat-tool-guard": "workspace:*",
|
||||
"@deepseek-ai/dsh-sandbox-local": "workspace:*",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# tui-agent
|
||||
|
||||
The full-screen interactive coding agent: DeepSeek V4, local bash and filesystem tools, compaction, subagents, workflows and fresh-agent Ralph iteration, `todo_write`, session modes (`/plan` enters plan mode, which has a reviewed exit), timeout/spill policy, and [`@deepseek-ai/dsh-tui-demo`](../../packages/examples/tui-demo).
|
||||
The full-screen interactive coding agent: DeepSeek V4, local bash and filesystem tools, compaction, subagents, workflows and fresh-agent Ralph iteration, `todo_write`, plan mode (`/plan` enters and `exit_plan_mode` reviews the exit), timeout/spill policy, and [`@deepseek-ai/dsh-tui-demo`](../../packages/examples/tui-demo).
|
||||
|
||||
## Run it
|
||||
|
||||
|
||||
@@ -47,8 +47,8 @@ flowchart LR
|
||||
cfg --> plugin_tui_tool_ralph
|
||||
plugin_tui_tool_todo["tool-todo<br/>@deepseek-ai/dsh-tool-todo"]
|
||||
cfg --> plugin_tui_tool_todo
|
||||
plugin_tui_mode["mode<br/>@deepseek-ai/dsh-mode"]
|
||||
cfg --> plugin_tui_mode
|
||||
plugin_tui_plan_mode["plan-mode<br/>@deepseek-ai/dsh-plan-mode"]
|
||||
cfg --> plugin_tui_plan_mode
|
||||
plugin_tui_fs_local["fs-local<br/>@deepseek-ai/dsh-fs-local"]
|
||||
cfg --> plugin_tui_fs_local
|
||||
plugin_tui_fs_policy["fs-policy<br/>@deepseek-ai/dsh-fs-policy"]
|
||||
@@ -83,7 +83,7 @@ flowchart LR
|
||||
| `tool-workflow` | `@deepseek-ai/dsh-tool-workflow` |
|
||||
| `tool-ralph` | `@deepseek-ai/dsh-tool-ralph` |
|
||||
| `tool-todo` | `@deepseek-ai/dsh-tool-todo` |
|
||||
| `mode` | `@deepseek-ai/dsh-mode` |
|
||||
| `plan-mode` | `@deepseek-ai/dsh-plan-mode` |
|
||||
| `fs-local` | `@deepseek-ai/dsh-fs-local` |
|
||||
| `fs-policy` | `@deepseek-ai/dsh-fs-policy` |
|
||||
| `tool-fs` | `@deepseek-ai/dsh-tool-fs` |
|
||||
|
||||
@@ -88,15 +88,12 @@
|
||||
- id: tool-todo
|
||||
name: '@deepseek-ai/dsh-tool-todo'
|
||||
|
||||
# Session modes (the shipped `plan` definition): the TUI's command registry
|
||||
# gains the plugin-registered /plan [message] command, and the exit review
|
||||
# rides the TUI's user-interaction provider.
|
||||
- id: mode
|
||||
name: '@deepseek-ai/dsh-mode'
|
||||
# Plan mode gives the TUI a plugin-owned /plan [message] command; the exit
|
||||
# review rides the TUI's user-interaction provider.
|
||||
- id: plan-mode
|
||||
name: '@deepseek-ai/dsh-plan-mode'
|
||||
config:
|
||||
modes:
|
||||
plan:
|
||||
section: |
|
||||
section: |
|
||||
You are in plan mode. Stay in plan mode until exit_plan_mode succeeds or the user switches the session mode. Imperative language to implement changes means plan the implementation, not execute it. A user's conversational agreement — including an answer confirming something you asked — approves nothing and does not end plan mode; fold the confirmed decision into the plan and submit it through exit_plan_mode.
|
||||
|
||||
Explore first. Use non-mutating reads, searches, static analysis, and checks to ground the plan in the actual repository. Do not edit or write files, change configuration, run formatters or code generation that rewrites tracked files, commit, or otherwise carry out the plan. Prefer existing functions and patterns over new machinery.
|
||||
|
||||
@@ -15,12 +15,10 @@
|
||||
- id: token-meter
|
||||
name: '@deepseek-ai/dsh-token-meter'
|
||||
|
||||
- id: mode
|
||||
name: '@deepseek-ai/dsh-mode'
|
||||
- id: plan-mode
|
||||
name: '@deepseek-ai/dsh-plan-mode'
|
||||
config:
|
||||
modes:
|
||||
plan:
|
||||
section: 'Stay in plan mode for this scripted TUI test.'
|
||||
section: 'Stay in plan mode for this scripted TUI test.'
|
||||
|
||||
- id: tui-agent
|
||||
name: '@deepseek-ai/dsh-tui-demo'
|
||||
|
||||
@@ -15,7 +15,7 @@ import * as FsPolicy from '@deepseek-ai/dsh-fs-policy'
|
||||
import * as ToolFs from '@deepseek-ai/dsh-tool-fs'
|
||||
import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek'
|
||||
import { installLlmReplay, parseSessionLog } from '@deepseek-ai/dsh-llm-replay'
|
||||
import ModesService, { PLAN_MODE } from '@deepseek-ai/dsh-mode'
|
||||
import PlanModeService from '@deepseek-ai/dsh-plan-mode'
|
||||
import TokenMeterService from '@deepseek-ai/dsh-token-meter'
|
||||
import type { Session, SessionEvent } from '@deepseek-ai/dsh-session'
|
||||
import { SessionId } from '@deepseek-ai/dsh-session'
|
||||
@@ -56,7 +56,7 @@ const SCENARIOS: Scenario[] = [
|
||||
name: 'multi-turn-conversation',
|
||||
composition: 'native',
|
||||
expectedTools: [],
|
||||
expectedEventCounts: { 'mode/set': 1 },
|
||||
expectedEventCounts: { 'plan/mode': 1 },
|
||||
enterPlanMode: true,
|
||||
recorded: true,
|
||||
},
|
||||
@@ -209,7 +209,7 @@ async function mountScenarioContext(
|
||||
await ctx.plugin(ToolRalph)
|
||||
await ctx.plugin(CommandService)
|
||||
if (scenario.enterPlanMode === true) {
|
||||
await ctx.plugin(ModesService, { modes: { plan: { section: 'Snapshot plan mode instructions.' } } })
|
||||
await ctx.plugin(PlanModeService, { section: 'Snapshot plan mode instructions.' })
|
||||
}
|
||||
if (scenario.composition === 'code' || scenario.composition === 'advanced') {
|
||||
await ctx.plugin(WorkerCodeRuntime, {})
|
||||
@@ -298,13 +298,13 @@ async function runScenario(scenario: Scenario): Promise<ScenarioResult> {
|
||||
expect(events.filter(event => event.type === type), `${scenario.name} must emit ${type}`).toHaveLength(count)
|
||||
}
|
||||
if (scenario.enterPlanMode === true) {
|
||||
expect(ctx.modes.get(agent)).toEqual({ current: PLAN_MODE })
|
||||
const modeSet = events.find(event => event.type === 'mode/set')
|
||||
expect(ctx.planMode.get(agent)).toEqual({ active: true })
|
||||
const planMode = events.find(event => event.type === 'plan/mode')
|
||||
const firstHeader = events.find(event => event.type === 'request/header')
|
||||
if (modeSet === undefined || firstHeader === undefined) {
|
||||
throw new Error('plan-mode command snapshot needs a mode/set before its first request/header')
|
||||
if (planMode === undefined || firstHeader === undefined) {
|
||||
throw new Error('plan-mode command snapshot needs plan/mode before its first request/header')
|
||||
}
|
||||
expect(modeSet.seq).toBeLessThan(firstHeader.seq)
|
||||
expect(planMode.seq).toBeLessThan(firstHeader.seq)
|
||||
expect(firstHeader.data.header.system).toContain('Snapshot plan mode instructions.')
|
||||
const firstMessage = events.find(event => event.type === 'user/message')
|
||||
expect(firstMessage?.data.content).toEqual([{ type: 'text', text: prompts[0] }])
|
||||
|
||||
Reference in New Issue
Block a user