Simplify session lifecycle and MCP cleanup
This commit is contained in:
@@ -14,6 +14,15 @@ import (
|
||||
// names live in the -32000 range with a structured `data.kind` so the
|
||||
// caller can branch on the error type rather than parsing strings.
|
||||
const (
|
||||
ErrorKindInvalidArgs = "invalid_args"
|
||||
ErrorKindInvalidKind = "invalid_kind"
|
||||
ErrorKindNeedsTrust = "needs_trust"
|
||||
ErrorKindRoleForbidden = "role_forbidden"
|
||||
ErrorKindNotRelated = "not_related"
|
||||
ErrorKindNotFound = "not_found"
|
||||
ErrorKindWrongKind = "wrong_kind"
|
||||
ErrorKindUnknownAgent = "unknown_agent"
|
||||
|
||||
codeParseError = -32700
|
||||
codeInvalidRequest = -32600
|
||||
codeMethodNotFound = -32601
|
||||
@@ -81,7 +90,10 @@ type ToolHost interface {
|
||||
TimerWait(callerID string, seconds float64, label string) (string, error)
|
||||
|
||||
// Scratchpads.
|
||||
Scratchpads() *scratchpad.Store
|
||||
ScratchpadList() ([]scratchpad.Entry, error)
|
||||
ScratchpadRead(name string) (content string, revision string, err error)
|
||||
ScratchpadWrite(name, content, expectedRevision string) (revision string, err error)
|
||||
ScratchpadAppend(name, content string) error
|
||||
|
||||
// Meta.
|
||||
WhoAmI(callerID string) WhoAmI
|
||||
@@ -105,14 +117,14 @@ type ProcessInfo struct {
|
||||
// ProcessInfo: includes pane geometry, cursor, and active screen.
|
||||
type ProcessStatus struct {
|
||||
ProcessInfo
|
||||
WorkingDir string `json:"working_dir,omitempty"`
|
||||
WorkingDir string `json:"working_dir,omitempty"`
|
||||
Argv []string `json:"argv,omitempty"`
|
||||
StartedAt string `json:"started_at,omitempty"`
|
||||
ActiveScreen string `json:"active_screen,omitempty"`
|
||||
Rows int `json:"rows,omitempty"`
|
||||
Cols int `json:"cols,omitempty"`
|
||||
Cursor Cursor `json:"cursor"`
|
||||
ScreenVersion int64 `json:"screen_version,omitempty"`
|
||||
StartedAt string `json:"started_at,omitempty"`
|
||||
ActiveScreen string `json:"active_screen,omitempty"`
|
||||
Rows int `json:"rows,omitempty"`
|
||||
Cols int `json:"cols,omitempty"`
|
||||
Cursor Cursor `json:"cursor"`
|
||||
ScreenVersion int64 `json:"screen_version,omitempty"`
|
||||
}
|
||||
|
||||
// Cursor matches SPEC §7's `{x, y}` payload.
|
||||
@@ -124,10 +136,10 @@ type Cursor struct {
|
||||
// ProjectStatus is what get_project_status returns — everything an
|
||||
// agent needs to orient itself in one call.
|
||||
type ProjectStatus struct {
|
||||
Project ProjectMeta `json:"project"`
|
||||
Caller WhoAmI `json:"caller"`
|
||||
Processes []ProcessInfo `json:"processes"`
|
||||
Scratchpads []scratchpad.Entry `json:"scratchpads"`
|
||||
Project ProjectMeta `json:"project"`
|
||||
Caller WhoAmI `json:"caller"`
|
||||
Processes []ProcessInfo `json:"processes"`
|
||||
Scratchpads []scratchpad.Entry `json:"scratchpads"`
|
||||
}
|
||||
|
||||
// ProjectMeta is the project root info echoed in many payloads.
|
||||
@@ -178,20 +190,20 @@ type PortSighting struct {
|
||||
|
||||
// SpawnAgentArgs is the input shape for spawn_agent.
|
||||
type SpawnAgentArgs struct {
|
||||
Agent string `json:"agent"`
|
||||
AgentInstructions string `json:"agent_instructions"`
|
||||
Name string `json:"name"`
|
||||
Agent string `json:"agent"`
|
||||
AgentInstructions string `json:"agent_instructions"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// SpawnProcessArgs is the input shape for spawn_process.
|
||||
type SpawnProcessArgs struct {
|
||||
Kind string `json:"kind"` // "terminal" | "command"
|
||||
Preset string `json:"preset"`
|
||||
Argv []string `json:"argv"`
|
||||
Name string `json:"name"`
|
||||
WorkingDir string `json:"working_dir"`
|
||||
Kind string `json:"kind"` // "terminal" | "command"
|
||||
Preset string `json:"preset"`
|
||||
Argv []string `json:"argv"`
|
||||
Name string `json:"name"`
|
||||
WorkingDir string `json:"working_dir"`
|
||||
Env map[string]string `json:"env"`
|
||||
Shell bool `json:"shell"`
|
||||
Shell bool `json:"shell"`
|
||||
}
|
||||
|
||||
// SendInputArgs is the input shape for send_input — covers text /
|
||||
@@ -214,12 +226,12 @@ type SendInputResult struct {
|
||||
|
||||
// WhoAmI is the whoami return shape.
|
||||
type WhoAmI struct {
|
||||
ProcessID string `json:"process_id"`
|
||||
Name string `json:"name"`
|
||||
Role CallerRole `json:"role"`
|
||||
ParentProcessID string `json:"parent_process_id,omitempty"`
|
||||
ProcessID string `json:"process_id"`
|
||||
Name string `json:"name"`
|
||||
Role CallerRole `json:"role"`
|
||||
ParentProcessID string `json:"parent_process_id,omitempty"`
|
||||
Project ProjectMeta `json:"project"`
|
||||
AvailableTools []string `json:"available_tools"`
|
||||
AvailableTools []string `json:"available_tools"`
|
||||
}
|
||||
|
||||
// HelpResponse is the help return shape.
|
||||
@@ -332,7 +344,9 @@ func callTool(h ToolHost, callerID, method string, params json.RawMessage) (any,
|
||||
return mapToolResult(info, err)
|
||||
|
||||
case "start_process":
|
||||
var p struct{ ProcessID string `json:"process_id"` }
|
||||
var p struct {
|
||||
ProcessID string `json:"process_id"`
|
||||
}
|
||||
if err := unmarshalParams(params, &p); err != nil {
|
||||
return nil, codeInvalidParams, err.Error(), nil
|
||||
}
|
||||
@@ -364,7 +378,9 @@ func callTool(h ToolHost, callerID, method string, params json.RawMessage) (any,
|
||||
return mapToolResult(info, err)
|
||||
|
||||
case "close_process":
|
||||
var p struct{ ProcessID string `json:"process_id"` }
|
||||
var p struct {
|
||||
ProcessID string `json:"process_id"`
|
||||
}
|
||||
if err := unmarshalParams(params, &p); err != nil {
|
||||
return nil, codeInvalidParams, err.Error(), nil
|
||||
}
|
||||
@@ -387,7 +403,9 @@ func callTool(h ToolHost, callerID, method string, params json.RawMessage) (any,
|
||||
return "ok", 0, "", nil
|
||||
|
||||
case "select_process":
|
||||
var p struct{ ProcessID string `json:"process_id"` }
|
||||
var p struct {
|
||||
ProcessID string `json:"process_id"`
|
||||
}
|
||||
if err := unmarshalParams(params, &p); err != nil {
|
||||
return nil, codeInvalidParams, err.Error(), nil
|
||||
}
|
||||
@@ -397,12 +415,16 @@ func callTool(h ToolHost, callerID, method string, params json.RawMessage) (any,
|
||||
return "ok", 0, "", nil
|
||||
|
||||
case "list_processes":
|
||||
var p struct{ Kind string `json:"kind"` }
|
||||
var p struct {
|
||||
Kind string `json:"kind"`
|
||||
}
|
||||
_ = unmarshalParamsOptional(params, &p)
|
||||
return h.ListProcesses(callerID, p.Kind), 0, "", nil
|
||||
|
||||
case "get_process_status":
|
||||
var p struct{ ProcessID string `json:"process_id"` }
|
||||
var p struct {
|
||||
ProcessID string `json:"process_id"`
|
||||
}
|
||||
if err := unmarshalParams(params, &p); err != nil {
|
||||
return nil, codeInvalidParams, err.Error(), nil
|
||||
}
|
||||
@@ -490,7 +512,9 @@ func callTool(h ToolHost, callerID, method string, params json.RawMessage) (any,
|
||||
return map[string]any{"matched": matched, "snippet": snippet}, 0, "", nil
|
||||
|
||||
case "get_process_ports":
|
||||
var p struct{ ProcessID string `json:"process_id"` }
|
||||
var p struct {
|
||||
ProcessID string `json:"process_id"`
|
||||
}
|
||||
if err := unmarshalParams(params, &p); err != nil {
|
||||
return nil, codeInvalidParams, err.Error(), nil
|
||||
}
|
||||
@@ -552,18 +576,20 @@ func callTool(h ToolHost, callerID, method string, params json.RawMessage) (any,
|
||||
return map[string]string{"timer_id": id}, 0, "", nil
|
||||
|
||||
case "scratchpad_list":
|
||||
entries, err := h.Scratchpads().List()
|
||||
entries, err := h.ScratchpadList()
|
||||
if err != nil {
|
||||
return nil, codeInternal, err.Error(), nil
|
||||
}
|
||||
return entries, 0, "", nil
|
||||
|
||||
case "scratchpad_read":
|
||||
var p struct{ Name string `json:"name"` }
|
||||
var p struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
if err := unmarshalParams(params, &p); err != nil {
|
||||
return nil, codeInvalidParams, err.Error(), nil
|
||||
}
|
||||
content, rev, err := h.Scratchpads().Read(p.Name)
|
||||
content, rev, err := h.ScratchpadRead(p.Name)
|
||||
if err != nil {
|
||||
return nil, codeInternal, err.Error(), nil
|
||||
}
|
||||
@@ -578,7 +604,7 @@ func callTool(h ToolHost, callerID, method string, params json.RawMessage) (any,
|
||||
if err := unmarshalParams(params, &p); err != nil {
|
||||
return nil, codeInvalidParams, err.Error(), nil
|
||||
}
|
||||
rev, err := h.Scratchpads().Write(p.Name, p.Content, p.ExpectedRevision)
|
||||
rev, err := h.ScratchpadWrite(p.Name, p.Content, p.ExpectedRevision)
|
||||
if err != nil {
|
||||
// Optimistic-concurrency miss returns ok:false + current_revision
|
||||
// rather than a JSON-RPC error so callers can re-read + merge.
|
||||
@@ -598,7 +624,7 @@ func callTool(h ToolHost, callerID, method string, params json.RawMessage) (any,
|
||||
if err := unmarshalParams(params, &p); err != nil {
|
||||
return nil, codeInvalidParams, err.Error(), nil
|
||||
}
|
||||
if err := h.Scratchpads().Append(p.Name, p.Content); err != nil {
|
||||
if err := h.ScratchpadAppend(p.Name, p.Content); err != nil {
|
||||
return nil, codeInternal, err.Error(), nil
|
||||
}
|
||||
return map[string]any{"ok": true}, 0, "", nil
|
||||
@@ -607,7 +633,9 @@ func callTool(h ToolHost, callerID, method string, params json.RawMessage) (any,
|
||||
return h.WhoAmI(callerID), 0, "", nil
|
||||
|
||||
case "help":
|
||||
var p struct{ Topic string `json:"topic"` }
|
||||
var p struct {
|
||||
Topic string `json:"topic"`
|
||||
}
|
||||
_ = unmarshalParamsOptional(params, &p)
|
||||
return h.Help(callerID, p.Topic), 0, "", nil
|
||||
}
|
||||
@@ -632,17 +660,19 @@ func mapToolError(err error) (any, int, string, any) {
|
||||
if errors.As(err, &te) {
|
||||
code := codeInternal
|
||||
switch te.Kind {
|
||||
case "needs_trust":
|
||||
case ErrorKindInvalidArgs, ErrorKindInvalidKind:
|
||||
code = codeInvalidParams
|
||||
case ErrorKindNeedsTrust:
|
||||
code = codeNeedsTrust
|
||||
case "role_forbidden":
|
||||
case ErrorKindRoleForbidden:
|
||||
code = codeRoleForbidden
|
||||
case "not_related":
|
||||
case ErrorKindNotRelated:
|
||||
code = codeNotRelated
|
||||
case "not_found":
|
||||
case ErrorKindNotFound:
|
||||
code = codeNotFound
|
||||
case "wrong_kind":
|
||||
case ErrorKindWrongKind:
|
||||
code = codeWrongKind
|
||||
case "unknown_agent":
|
||||
case ErrorKindUnknownAgent:
|
||||
code = codeUnknownAgent
|
||||
}
|
||||
return nil, code, te.Message, structuredKind(te.Kind)
|
||||
|
||||
Reference in New Issue
Block a user