v0.17.0 949637b

update_goal.go RawHistoryFolder

1package tools
2
3import (
4	"bytes"
5	"context"
6	"encoding/json"
7	"io"
8	"strings"
9
10	"github.com/bnema/zut/packages/core"
11	"github.com/bnema/zut/packages/provider"
12)
13
14const (
15	UpdateGoalToolName = "update_goal"
16	updateGoalSchema   = `{"type":"object","properties":{"status":{"type":"string","enum":["active","complete","blocked"],"description":"Set the next active goal, or mark the active goal complete or blocked."},"objective":{"type":"string","description":"Concrete objective for status active."},"mission_id":{"type":"string","description":"Mission identifier supplied in the active-goal context when setting a next goal."},"reason":{"type":"string","description":"Concise reason when the goal is blocked."}},"required":["status"],"additionalProperties":false}`
17)
18
19// UpdateGoalTool lets the manager set the next concrete active goal or settle
20// the current goal. Host-side persistence validates that a transition remains
21// within the session's active mission.
22type UpdateGoalTool struct{}
23
24type updateGoalArgs struct {
25	Status    string `json:"status"`
26	Objective string `json:"objective,omitempty"`
27	MissionID string `json:"mission_id,omitempty"`
28	Reason    string `json:"reason,omitempty"`
29}
30
31// GoalUpdate is trusted metadata returned only by UpdateGoalTool.
32type GoalUpdate struct {
33	Status    core.GoalStatus
34	Objective string
35	MissionID string
36	Reason    string
37}
38
39func (t *UpdateGoalTool) Name() string { return UpdateGoalToolName }
40
41func (t *UpdateGoalTool) Description() string {
42	return "Set the next concrete goal needed for the active user mission, or mark the active goal complete or blocked. Do not broaden the user's mission."
43}
44
45func (t *UpdateGoalTool) Schema() json.RawMessage { return json.RawMessage(updateGoalSchema) }
46
47func (t *UpdateGoalTool) Execute(_ context.Context, raw json.RawMessage, _ func(string)) (core.ToolResult, error) {
48	var args updateGoalArgs
49	decoder := json.NewDecoder(bytes.NewReader(raw))
50	decoder.DisallowUnknownFields()
51	if err := decoder.Decode(&args); err != nil {
52		return goalToolError("invalid arguments"), nil
53	}
54	if err := decoder.Decode(&struct{}{}); err != io.EOF {
55		return goalToolError("invalid arguments"), nil
56	}
57	args.Status = strings.TrimSpace(args.Status)
58	args.Objective = strings.TrimSpace(args.Objective)
59	args.MissionID = strings.TrimSpace(args.MissionID)
60	args.Reason = strings.TrimSpace(args.Reason)
61
62	var update GoalUpdate
63	switch args.Status {
64	case "active":
65		if args.Objective == "" {
66			return goalToolError("objective is required when setting a goal"), nil
67		}
68		update.Status = core.GoalActive
69		update.Objective = args.Objective
70		update.MissionID = args.MissionID
71	case "complete":
72		update.Status = core.GoalDone
73	case "blocked":
74		if args.Reason == "" {
75			return goalToolError("reason is required when blocking a goal"), nil
76		}
77		update.Status = core.GoalBlocked
78		update.Reason = args.Reason
79	default:
80		return goalToolError("status must be active, complete, or blocked"), nil
81	}
82
83	text := "autonomous goal marked " + string(update.Status)
84	if update.Reason != "" {
85		text += ": " + update.Reason
86	}
87	return core.ToolResult{
88		Content: []provider.Content{provider.TextBlock{Text: text}},
89		Details: update,
90	}, nil
91}
92
93// GoalUpdateFromResult extracts a valid update_goal state transition.
94func GoalUpdateFromResult(result core.ToolResult) (GoalUpdate, bool) {
95	if result.IsError {
96		return GoalUpdate{}, false
97	}
98	update, ok := result.Details.(GoalUpdate)
99	if !ok {
100		return GoalUpdate{}, false
101	}
102	if update.Status != core.GoalActive && update.Status != core.GoalDone && update.Status != core.GoalBlocked {
103		return GoalUpdate{}, false
104	}
105	if update.Status == core.GoalActive && strings.TrimSpace(update.Objective) == "" {
106		return GoalUpdate{}, false
107	}
108	if update.Status == core.GoalBlocked && strings.TrimSpace(update.Reason) == "" {
109		return GoalUpdate{}, false
110	}
111	return update, true
112}
113
114func goalToolError(message string) core.ToolResult {
115	return core.ToolResult{
116		Content: []provider.Content{provider.TextBlock{Text: message}},
117		IsError: true,
118	}
119}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 477

↑↓ moveenter openesc close