feat: tighten freestyle discovery and analyst handoff
This commit is contained in:
@@ -367,13 +367,22 @@ func (m *Model) applyServer(msg protocol.ServerMessage) {
|
||||
if s := m.session(msg.SessionID); s != nil {
|
||||
// The resolved gate names no tool on the wire; recover it from the pending
|
||||
// queue before dropping the gate, for the inline row.
|
||||
tool := ""
|
||||
tool, preview := "", ""
|
||||
for _, a := range s.PendingQueue {
|
||||
if a.RequestID == msg.RequestID {
|
||||
tool = a.ToolName
|
||||
preview = a.Preview
|
||||
break
|
||||
}
|
||||
}
|
||||
// Extract the target (file path for diffs, command for shell) from the
|
||||
// preview so the action row reads "approved file_write → healthcheck.sh".
|
||||
target := ""
|
||||
if tool == "file_write" && isUnifiedDiff(preview) {
|
||||
target = diffTarget(preview)
|
||||
} else if tool == "shell" && strings.HasPrefix(preview, "{") {
|
||||
target = previewTarget(preview)
|
||||
}
|
||||
// Drop just the resolved gate; any others stay queued and the band
|
||||
// advances to the next rather than vanishing entirely.
|
||||
s.removeApproval(msg.RequestID)
|
||||
@@ -383,7 +392,7 @@ func (m *Model) applyServer(msg protocol.ServerMessage) {
|
||||
}
|
||||
s.addEvent(msg.OccurredAt, "ApprovalResolved", detail)
|
||||
s.LastEventAt = nowMillis()
|
||||
m.appendAction(msg.SessionID, approvalIcon(msg.Outcome), approvalActionText(msg.Outcome, tool, msg.Reason))
|
||||
m.appendAction(msg.SessionID, approvalIcon(msg.Outcome), approvalActionText(msg.Outcome, tool, target, msg.Reason))
|
||||
}
|
||||
case protocol.TypeSessionSnapshot:
|
||||
m.onSnapshot(msg)
|
||||
@@ -580,11 +589,13 @@ func lastToolParams(s *Session, name string) []string {
|
||||
}
|
||||
|
||||
// paramSuffix renders pretty call args as a " (k=v · k=v)" suffix, or "" when there are none.
|
||||
// The clip is generous (200 cols) so real tool arguments like shell commands, grep patterns,
|
||||
// and file paths are legible — the panel width is the real limit.
|
||||
func paramSuffix(params []string) string {
|
||||
if len(params) == 0 {
|
||||
return ""
|
||||
}
|
||||
return " (" + clip(strings.Join(params, " · "), 56) + ")"
|
||||
return " (" + clip(strings.Join(params, " · "), 200) + ")"
|
||||
}
|
||||
|
||||
// actionToolText joins a tool label with a short, clipped result summary.
|
||||
@@ -608,7 +619,7 @@ func approvalIcon(outcome string) string {
|
||||
|
||||
// approvalActionText renders the inline approval row, noting an auto-approval that fired via a
|
||||
// standing grant (reason "grant:<id>").
|
||||
func approvalActionText(outcome, tool, reason string) string {
|
||||
func approvalActionText(outcome, tool, target, reason string) string {
|
||||
verb := "approved"
|
||||
switch outcome {
|
||||
case "REJECTED":
|
||||
@@ -620,12 +631,39 @@ func approvalActionText(outcome, tool, reason string) string {
|
||||
if tool != "" {
|
||||
txt = verb + " " + tool
|
||||
}
|
||||
if target != "" {
|
||||
txt += " → " + target
|
||||
}
|
||||
if strings.HasPrefix(reason, "grant:") {
|
||||
txt += " · via grant"
|
||||
}
|
||||
return txt
|
||||
}
|
||||
|
||||
// previewTarget extracts a short target label from a JSON preview payload
|
||||
// (e.g. {"argv":["bash","-c","curl ..."]} → the last argv element, truncated).
|
||||
func previewTarget(preview string) string {
|
||||
// Naive extraction: find "argv" array and return the last element
|
||||
if i := strings.Index(preview, `"argv":`); i >= 0 {
|
||||
rest := preview[i+len(`"argv":`):]
|
||||
if j := strings.IndexByte(rest, '['); j >= 0 {
|
||||
argv := rest[j:]
|
||||
if k := strings.IndexByte(argv, ']'); k >= 0 {
|
||||
argv = argv[:k+1]
|
||||
}
|
||||
// Parse comma-separated strings, grab the last non-empty
|
||||
parts := strings.Split(strings.Trim(argv, "[]"), ",")
|
||||
for i := len(parts) - 1; i >= 0; i-- {
|
||||
candidate := strings.Trim(strings.Trim(parts[i], ` "`), `"`)
|
||||
if candidate != "" {
|
||||
return clip(candidate, 40)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// onSessionAnnounced fills in a session's workflow identity (the announce is the
|
||||
// only event carrying workflowId) and applies auto-focus. The session entry itself
|
||||
// was already created by the auto-vivify path in applyServer.
|
||||
|
||||
Reference in New Issue
Block a user