返回 DeepSeek-Reasonix
policy.go
根目录 / internal / planmode / policy.go
1 package planmode
2
3 import (
4 "encoding/json"
5 "fmt"
6 "strings"
7 )
8
9 // Marker is the model-facing plan-mode instruction block. It rides in the user
10 // turn, not the system prompt or tool schema, so plan toggles preserve cache shape.
11 const Marker = "[Plan mode — planning workflow. Gather context, ask clarifying questions with ask, maintain planning state with todo_write, and delegate focused research when useful. Do not begin implementation in this mode: avoid file writes, unsafe shell commands, capability installation, memory mutation, writer-capable delegation, long-lived process control, or execution-step completion. This is a workflow instruction, not a permission boundary; every tool call remains governed by the active Permissions and Sandbox policy. Before planning, if a decision that is genuinely the user's — tech stack, an ambiguous requirement, scope, an irreversible choice — would materially shape the plan and you can't settle it from the codebase or a sensible default, use the ask tool to clarify it first; otherwise pick the obvious default and state the assumption in the plan instead of asking. Then present a LAYERED plan as your reply and stop. Structure the plan as a two-level markdown list so it becomes a layered task list: each PHASE is a top-level numbered list item (a coherent milestone, e.g. \"1. Add the config loader\"), and each phase's concrete, verifiable sub-steps are bullets indented beneath it (e.g. \" - parse the TOML into Config\"). Use plain numbered list items for phases — do NOT write phases as markdown headings (##, ###) — so both levels parse. Keep phases few (about 2-6). The user will be asked to approve the plan before the workflow switches to implementation.]"
12
13 // PlanSafety is a tool's explicit stance on whether the action belongs in the
14 // planning phase. It is deliberately not a write-safety classification: ordinary
15 // readers and writers both continue to Permissions/Sandbox.
16 type PlanSafety int
17
18 const (
19 // PlanSafetyUnknown is the default. The call continues to Permissions/Sandbox.
20 PlanSafetyUnknown PlanSafety = iota
21 // PlanSafetySafe explicitly confirms that the call makes sense while planning.
22 PlanSafetySafe
23 // PlanSafetyUnsafe opts a tool out of the planning phase even when it is
24 // side-effect-free. complete_step is the canonical example.
25 PlanSafetyUnsafe
26 )
27
28 // Call is the plan-mode view of one tool invocation. ReadOnly and Args remain
29 // for source compatibility with older callers; they do not decide phase
30 // availability because Permissions/Sandbox own safety.
31 type Call struct {
32 Name string
33 ReadOnly bool
34 Safety PlanSafety
35 Args json.RawMessage
36 }
37
38 // Decision reports whether phase semantics refuse a call and why.
39 type Decision struct {
40 Blocked bool
41 Message string
42 }
43
44 // ReadOnlyCommandTrust is retained for source compatibility with the legacy
45 // Plan bash trust bridge. Decide no longer produces this request: bash safety is
46 // classified by Permissions, and read-only subagents enforce their own runner
47 // boundary directly.
48 type ReadOnlyCommandTrust struct {
49 Command string
50 Prefix string
51 }
52
53 // Policy is retained so existing config/assembly code can carry legacy
54 // plan_mode_* fields without breaking old data. Those fields no longer grant or
55 // revoke execution in the main Plan workflow.
56 type Policy struct {
57 AllowedTools []string
58 ReadOnlyCommands []string
59 }
60
61 // Decide applies phase semantics only. Plan is a collaboration workflow, not a
62 // security boundary: every ordinary call proceeds to the same permission and
63 // sandbox gates used outside Plan. A tool may explicitly opt out when executing
64 // it during planning is semantically invalid.
65 func (Policy) Decide(call Call) Decision {
66 if call.Safety != PlanSafetyUnsafe {
67 return Decision{}
68 }
69 name := strings.TrimSpace(call.Name)
70 if name == "complete_step" {
71 return Decision{
72 Blocked: true,
73 Message: "blocked: complete_step is only available after plan approval. While planning, keep task state with todo_write and present the plan for user approval.",
74 }
75 }
76 return Decision{
77 Blocked: true,
78 Message: fmt.Sprintf("blocked: %q is not available during the planning workflow. Finish or exit Plan mode before calling it.", name),
79 }
80 }
81
81 lines GO