feat(recovery,context): tier-2 intent-holder arbiter + remaining-delta pinning + surfaced signal events
- recovery: two-tier repair ladder — owner then arbiter (recovery stage recast
as intent-holder, holds initial intent, reconciles cross-file contract disputes)
with independent INTENT_ROUTE_BUDGET; RecoveryRoutingTest coverage
- context: remaining-delta checklist pinning (RemainingDelta{Pinning,Entry}Test)
- surface write-only events (session name, quality signals) into stats/browse
- parseToolArguments lenient-Json fallback for bare-key drift
- tools: ChildProcess seam
This commit is contained in:
@@ -119,6 +119,7 @@ type ToolRecord struct {
|
||||
Name string
|
||||
Tier int
|
||||
Status ToolStatus
|
||||
Params []string // pretty "key=value" call args, from tool.started
|
||||
}
|
||||
|
||||
// ManifestTool is a declared (not-yet-run) tool from a stage manifest.
|
||||
@@ -208,6 +209,7 @@ type Session struct {
|
||||
Status string
|
||||
WorkflowID string
|
||||
Name string
|
||||
named bool // Name came from a session.renamed (intent-derived title); don't clobber with workflowId
|
||||
LastEventAt int64
|
||||
CurrentStage string
|
||||
PlanGoal string
|
||||
@@ -429,6 +431,11 @@ type Model struct {
|
||||
// the OUTPUT transcript; they always stay in the EVENTS panel. Persisted in tui-prefs.json.
|
||||
actionsHidden bool
|
||||
|
||||
// thinkingShown reveals the model's reasoning/thinking blocks in the OUTPUT transcript.
|
||||
// Off by default (collapsed to a one-line summary) so the trace doesn't drown the answer;
|
||||
// toggled from the palette ("thinking"). Persisted in tui-prefs.json.
|
||||
thinkingShown bool
|
||||
|
||||
// outputScroll is how many rows up from the bottom the OUTPUT transcript is scrolled
|
||||
// (0 = tail-follow the newest output). PgUp/PgDn + ctrl+u/d move it; esc snaps back.
|
||||
outputScroll int
|
||||
@@ -504,6 +511,7 @@ func NewModel(client *ws.Client) Model {
|
||||
transcriptSel: -1,
|
||||
sbHidden: loadStatusbarHidden(),
|
||||
actionsHidden: loadPrefs().InlineActionsHidden,
|
||||
thinkingShown: loadPrefs().ThinkingShown,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -532,7 +532,7 @@ func (m Model) eventInspectorModal() string {
|
||||
fg = t.P.FgStrong
|
||||
}
|
||||
row := marker + mbg(t, padRaw(e.Time, 8)+" ", t.P.Faint) +
|
||||
lipgloss.NewStyle().Foreground(t.categoryColor(cat)).Background(t.P.BgPanel).Render(padRaw("["+cat+"]", 11)) + " " +
|
||||
lipgloss.NewStyle().Foreground(t.categoryColor(cat)).Background(t.P.BgPanel).Render(padRaw("["+cat+"]", 11)) + mbg(t, " ", t.P.Fg) +
|
||||
lipgloss.NewStyle().Foreground(fg).Background(t.P.BgPanel).Render(padRaw(e.Type, 18)) +
|
||||
mbg(t, " "+truncate(e.Detail, detailBudget), t.P.Dim)
|
||||
b.WriteString(row + "\n")
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
type prefs struct {
|
||||
StatusbarHidden []string `json:"statusbarHidden"`
|
||||
InlineActionsHidden bool `json:"inlineActionsHidden"`
|
||||
ThinkingShown bool `json:"thinkingShown"`
|
||||
}
|
||||
|
||||
// statusSegment is one toggleable status-bar segment. The always-on segments (correx label,
|
||||
@@ -97,3 +98,10 @@ func saveInlineActionsHidden(hidden bool) {
|
||||
p.InlineActionsHidden = hidden
|
||||
savePrefs(p)
|
||||
}
|
||||
|
||||
// saveThinkingShown persists the reasoning-blocks toggle, preserving other prefs fields.
|
||||
func saveThinkingShown(shown bool) {
|
||||
p := loadPrefs()
|
||||
p.ThinkingShown = shown
|
||||
savePrefs(p)
|
||||
}
|
||||
|
||||
@@ -352,6 +352,17 @@ func matrixCases() []matrixCase {
|
||||
want: []string{"corx-ws"},
|
||||
},
|
||||
|
||||
// --- session rename (status bar shows intent-derived title) ---
|
||||
{
|
||||
name: "session.renamed",
|
||||
kinds: []string{protocol.TypeSessionRenamed},
|
||||
surface: surfaceStatusBar,
|
||||
build: func() protocol.ServerMessage {
|
||||
return msg(protocol.TypeSessionRenamed, func(m *protocol.ServerMessage) { m.Name = "Add Auth Layer" })
|
||||
},
|
||||
want: []string{"Add Auth Layer"},
|
||||
},
|
||||
|
||||
// --- approval gate (docked band card) ---
|
||||
{
|
||||
name: "approval.required",
|
||||
|
||||
@@ -96,6 +96,8 @@ func (m *Model) applyServer(msg protocol.ServerMessage) {
|
||||
switch msg.Type {
|
||||
case protocol.TypeSessionAnnounced:
|
||||
m.onSessionAnnounced(msg)
|
||||
case protocol.TypeSessionRenamed:
|
||||
m.onSessionRenamed(msg)
|
||||
case protocol.TypeRouterNarration:
|
||||
entry := RouterEntry{Role: "narration_llm", Content: msg.Content}
|
||||
if msg.LatencyMs != nil && msg.TotalTokens != nil {
|
||||
@@ -198,6 +200,11 @@ func (m *Model) applyServer(msg protocol.ServerMessage) {
|
||||
s.StageTokensUsed = *msg.TotalTokens
|
||||
}
|
||||
s.addEvent(msg.OccurredAt, "InferenceCompleted", msg.StageID)
|
||||
// Thread the model's reasoning trace into the transcript as a collapsed "thinking"
|
||||
// row (revealed via the palette). Skipped when the model emits no separate channel.
|
||||
if strings.TrimSpace(msg.Reasoning) != "" {
|
||||
m.appendRouter(msg.SessionID, RouterEntry{Role: "thinking", Content: msg.Reasoning})
|
||||
}
|
||||
}
|
||||
case protocol.TypeInferenceTimeout:
|
||||
if s := m.session(msg.SessionID); s != nil {
|
||||
@@ -217,7 +224,7 @@ func (m *Model) applyServer(msg protocol.ServerMessage) {
|
||||
case protocol.TypeToolStarted:
|
||||
if s := m.session(msg.SessionID); s != nil {
|
||||
s.Active = true
|
||||
s.Tools = append(s.Tools, ToolRecord{Name: msg.ToolName, Status: ToolStarted})
|
||||
s.Tools = append(s.Tools, ToolRecord{Name: msg.ToolName, Status: ToolStarted, Params: msg.Params})
|
||||
if len(s.Tools) > 8 {
|
||||
s.Tools = s.Tools[len(s.Tools)-8:]
|
||||
}
|
||||
@@ -240,7 +247,15 @@ func (m *Model) applyServer(msg protocol.ServerMessage) {
|
||||
m.appendRouter(msg.SessionID, RouterEntry{Role: "tool", Content: *msg.Diff})
|
||||
} else {
|
||||
label := msg.ToolName
|
||||
if len(msg.AffectedEntities) > 0 {
|
||||
// Prefer the actual call args (path=…, command="…") over the affected-entities
|
||||
// fallback: params show *what was asked*, not just what got touched.
|
||||
if s := m.session(msg.SessionID); s != nil {
|
||||
if suffix := paramSuffix(lastToolParams(s, msg.ToolName)); suffix != "" {
|
||||
label += suffix
|
||||
} else if len(msg.AffectedEntities) > 0 {
|
||||
label += " " + strings.Join(msg.AffectedEntities, ", ")
|
||||
}
|
||||
} else if len(msg.AffectedEntities) > 0 {
|
||||
label += " " + strings.Join(msg.AffectedEntities, ", ")
|
||||
}
|
||||
m.appendAction(msg.SessionID, "✓", actionToolText(label, msg.Summary))
|
||||
@@ -542,6 +557,25 @@ func countSuffix(added, removed int) string {
|
||||
return " (+" + itoa(added) + " −" + itoa(removed) + ")"
|
||||
}
|
||||
|
||||
// lastToolParams returns the call args of the most recent invocation of the named tool. The ReAct
|
||||
// loop runs one tool at a time per session, so the last started record is the one now completing.
|
||||
func lastToolParams(s *Session, name string) []string {
|
||||
for i := len(s.Tools) - 1; i >= 0; i-- {
|
||||
if s.Tools[i].Name == name {
|
||||
return s.Tools[i].Params
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// paramSuffix renders pretty call args as a " (k=v · k=v)" suffix, or "" when there are none.
|
||||
func paramSuffix(params []string) string {
|
||||
if len(params) == 0 {
|
||||
return ""
|
||||
}
|
||||
return " (" + clip(strings.Join(params, " · "), 56) + ")"
|
||||
}
|
||||
|
||||
// actionToolText joins a tool label with a short, clipped result summary.
|
||||
func actionToolText(label, summary string) string {
|
||||
// Collapse to a single line first: tool summaries (dir listings, file heads)
|
||||
@@ -589,7 +623,9 @@ func (m *Model) onSessionAnnounced(msg protocol.ServerMessage) {
|
||||
s.Status = "ACTIVE"
|
||||
if msg.WorkflowID != "" {
|
||||
s.WorkflowID = msg.WorkflowID
|
||||
s.Name = msg.WorkflowID
|
||||
if !s.named {
|
||||
s.Name = msg.WorkflowID
|
||||
}
|
||||
}
|
||||
s.LastEventAt = nowMillis()
|
||||
if m.pendingWorkflowFocus {
|
||||
@@ -601,6 +637,19 @@ func (m *Model) onSessionAnnounced(msg protocol.ServerMessage) {
|
||||
}
|
||||
}
|
||||
|
||||
// onSessionRenamed applies the intent-derived title from session.renamed, replacing the
|
||||
// opaque workflow id shown until the naming inference completed. The named flag pins it so a
|
||||
// later announce (e.g. on reconnect replay) doesn't revert the display back to the workflow id.
|
||||
func (m *Model) onSessionRenamed(msg protocol.ServerMessage) {
|
||||
if msg.Name == "" {
|
||||
return
|
||||
}
|
||||
s := m.ensureSession(msg.SessionID)
|
||||
s.Name = msg.Name
|
||||
s.named = true
|
||||
s.LastEventAt = nowMillis()
|
||||
}
|
||||
|
||||
func (m *Model) onApprovalRequired(msg protocol.ServerMessage) {
|
||||
risk := "unknown"
|
||||
var rationale []string
|
||||
@@ -710,6 +759,70 @@ func (m *Model) onSnapshot(msg protocol.ServerMessage) {
|
||||
if m.selectedID == "" {
|
||||
m.selectedID = msg.SessionID
|
||||
}
|
||||
m.restoreTranscript(msg.SessionID, msg.Transcript)
|
||||
}
|
||||
|
||||
// restoreTranscript rebuilds the OUTPUT transcript for a reopened session from the snapshot's raw
|
||||
// source rows, reusing the same formatters the live path uses (diffSummary/paramSuffix/…). Replaces
|
||||
// (not appends) so a reconnect with an existing transcript doesn't duplicate rows.
|
||||
func (m *Model) restoreTranscript(sid string, rows []protocol.TranscriptRowDto) {
|
||||
if len(rows) == 0 {
|
||||
return
|
||||
}
|
||||
out := make([]RouterEntry, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
switch r.Kind {
|
||||
case "chat":
|
||||
role := "router"
|
||||
if r.Role == "USER" {
|
||||
role = "user"
|
||||
}
|
||||
e := RouterEntry{Role: role, Content: r.Content}
|
||||
if r.LatencyMs != nil && r.TotalTokens != nil {
|
||||
e.Metrics = &TurnMetrics{LatencyMs: *r.LatencyMs, TotalTokens: *r.TotalTokens}
|
||||
}
|
||||
out = append(out, e)
|
||||
case "narration":
|
||||
e := RouterEntry{Role: "narration_llm", Content: r.Content}
|
||||
if r.LatencyMs != nil && r.TotalTokens != nil {
|
||||
e.Metrics = &TurnMetrics{LatencyMs: *r.LatencyMs, TotalTokens: *r.TotalTokens}
|
||||
}
|
||||
out = append(out, e)
|
||||
case "thinking":
|
||||
if strings.TrimSpace(r.Content) != "" {
|
||||
out = append(out, RouterEntry{Role: "thinking", Content: r.Content})
|
||||
}
|
||||
case "tool":
|
||||
out = append(out, toolTranscriptRows(r)...)
|
||||
}
|
||||
}
|
||||
m.routerMessages[sid] = out
|
||||
}
|
||||
|
||||
// toolTranscriptRows mirrors the live TypeToolCompleted/Failed/Rejected handling: a diff becomes a
|
||||
// ✎ write row + the collapsed raw-diff row; otherwise a single ✓/✗/✕ action row.
|
||||
func toolTranscriptRows(r protocol.TranscriptRowDto) []RouterEntry {
|
||||
if r.Diff != nil && *r.Diff != "" {
|
||||
path, add, del := diffSummary(*r.Diff)
|
||||
return []RouterEntry{
|
||||
{Role: "action", Icon: "✎", Content: "wrote " + path + countSuffix(add, del)},
|
||||
{Role: "tool", Content: *r.Diff},
|
||||
}
|
||||
}
|
||||
switch r.Status {
|
||||
case "failed":
|
||||
return []RouterEntry{{Role: "action", Icon: "✗", Content: actionToolText(r.ToolName+" failed", r.Summary)}}
|
||||
case "rejected":
|
||||
return []RouterEntry{{Role: "action", Icon: "✕", Content: actionToolText(r.ToolName+" blocked", r.Summary)}}
|
||||
default:
|
||||
label := r.ToolName
|
||||
if suffix := paramSuffix(r.Params); suffix != "" {
|
||||
label += suffix
|
||||
} else if len(r.AffectedEntities) > 0 {
|
||||
label += " " + strings.Join(r.AffectedEntities, ", ")
|
||||
}
|
||||
return []RouterEntry{{Role: "action", Icon: "✓", Content: actionToolText(label, r.Summary)}}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) touch(id, status string) {
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
type SessionSummary struct {
|
||||
SessionID string `json:"sessionId"`
|
||||
Status string `json:"status"`
|
||||
Name string `json:"name"`
|
||||
Intent string `json:"intent"`
|
||||
WorkflowID string `json:"workflowId"`
|
||||
StageCount int `json:"stageCount"`
|
||||
@@ -222,7 +223,12 @@ func (m Model) sessionListRow(i int) string {
|
||||
statusCell := lipgloss.NewStyle().Foreground(statusColor(t, s.Status)).Background(t.P.BgPanel).Bold(true).
|
||||
Render(padRaw(statusLabel(s.Status), 9))
|
||||
|
||||
stage := s.WorkflowID
|
||||
// Prefer the human session name (SessionNamedEvent, intent-derived) over the raw
|
||||
// workflow id — it's what the operator recognizes a session by.
|
||||
stage := s.Name
|
||||
if stage == "" {
|
||||
stage = s.WorkflowID
|
||||
}
|
||||
if s.StageCount > 0 {
|
||||
stage += " ·" + itoa(s.StageCount) + "stg"
|
||||
}
|
||||
|
||||
@@ -1160,6 +1160,7 @@ func paletteCommands() []paletteCmd {
|
||||
{"rail", "", "idle rail", "show / hide the idle status + keys rail"},
|
||||
{"panel", "d", "right panel", "in-session: events → changes → off"},
|
||||
{"actions", "", "inline actions", "show / hide tool & action rows in output"},
|
||||
{"thinking", "", "thinking blocks", "show / hide model reasoning in output"},
|
||||
{"help", "?", "help", "keybinding cheat-sheet"},
|
||||
{"mode", "s", "toggle mode", "switch chat / steering"},
|
||||
{"cancel", "c", "cancel session", "stop the selected session"},
|
||||
@@ -1217,6 +1218,9 @@ func (m Model) execPalette(id string) (tea.Model, tea.Cmd) {
|
||||
m.cycleRightPanel()
|
||||
case "actions":
|
||||
m.toggleInlineActions()
|
||||
case "thinking":
|
||||
m.thinkingShown = !m.thinkingShown
|
||||
saveThinkingShown(m.thinkingShown)
|
||||
case "help":
|
||||
m.overlay = OverlayHelp
|
||||
m.modalScroll = 0
|
||||
|
||||
@@ -573,7 +573,7 @@ func (m Model) launcherInput(w int) string {
|
||||
box := strings.Join(boxLines, "\n")
|
||||
|
||||
wf := lipgloss.NewStyle().Foreground(t.P.Accent2).Background(t.P.Bg).Render(m.launcherWfName())
|
||||
leftSub := " " + wf + t.span(" · ", t.P.Faint) + t.span(m.currentModel, t.P.Faint)
|
||||
leftSub := t.span(" ", t.P.Faint) + wf + t.span(" · ", t.P.Faint) + t.span(m.currentModel, t.P.Faint)
|
||||
hints := t.span("Tab wf · ⌥↵/^J newline · enter ↵ ", t.P.Faint)
|
||||
return box + "\n" + m.justify(leftSub, hints, w, t.P.Bg)
|
||||
}
|
||||
@@ -823,6 +823,23 @@ func (m Model) buildTranscriptRows(w int) ([]string, []int) {
|
||||
}
|
||||
case "tool":
|
||||
rows = append(rows, t.span(toolRowSummary(e.Content), t.P.Dim))
|
||||
case "thinking":
|
||||
// Collapsed by default to a single muted line so the reasoning trace doesn't bury
|
||||
// the answer; the palette "thinking" toggle reveals the full dimmed block.
|
||||
brain := lipgloss.NewStyle().Foreground(t.P.Faint).Background(t.P.Bg).Render("✼")
|
||||
if !m.thinkingShown {
|
||||
n := strings.Count(strings.TrimRight(e.Content, "\n"), "\n") + 1
|
||||
rows = append(rows, brain+t.span(" thinking ("+plural(n, "line")+") — palette: thinking", t.P.Faint))
|
||||
break
|
||||
}
|
||||
lines := wrap(e.Content, w-2)
|
||||
for i, ln := range lines {
|
||||
if i == 0 {
|
||||
rows = append(rows, brain+t.span(" ", t.P.Bg)+t.span(ln, t.P.Faint))
|
||||
} else {
|
||||
rows = append(rows, t.span(" ", t.P.Bg)+t.span(ln, t.P.Faint))
|
||||
}
|
||||
}
|
||||
case "narration_llm":
|
||||
diamond := lipgloss.NewStyle().Foreground(t.P.Accent).Background(t.P.Bg).Bold(true).Render("◆")
|
||||
lines := wrap(e.Content, w-2)
|
||||
@@ -913,7 +930,7 @@ func (m Model) eventRows(w, h int) []string {
|
||||
catCell := lipgloss.NewStyle().Foreground(t.categoryColor(cat)).Background(t.P.BgPanel).
|
||||
Render(" " + padRaw(cat, 9) + " ")
|
||||
evRows = append(evRows,
|
||||
t.span(e.Time+" ", t.P.Faint)+catCell+" "+
|
||||
t.span(e.Time+" ", t.P.Faint)+catCell+t.span(" ", t.P.Bg)+
|
||||
t.span(padRaw(e.Type, 18), t.P.Fg)+t.span(e.Detail, t.P.Dim))
|
||||
}
|
||||
// Show the latest events that fit under the 2-line header (box inner = h-2).
|
||||
|
||||
@@ -36,6 +36,7 @@ const (
|
||||
TypeApprovalResolved = "approval.resolved"
|
||||
TypeClarification = "clarification.required"
|
||||
TypeWorkflowProposed = "workflow.proposed"
|
||||
TypeSessionRenamed = "session.renamed"
|
||||
TypeWorkspaceBound = "session.workspace_bound"
|
||||
TypeStageManifest = "stage.tool_manifest"
|
||||
TypeProviderStatus = "provider.status_changed"
|
||||
@@ -70,30 +71,34 @@ const (
|
||||
type ServerMessage struct {
|
||||
Type string `json:"type"`
|
||||
|
||||
SessionID string `json:"sessionId"`
|
||||
WorkflowID string `json:"workflowId"`
|
||||
StageID string `json:"stageId"`
|
||||
ToolName string `json:"toolName"`
|
||||
Role string `json:"role"`
|
||||
TurnID string `json:"turnId"`
|
||||
Reason string `json:"reason"`
|
||||
Summary string `json:"outputSummary"`
|
||||
Response string `json:"responseText"`
|
||||
LastOutput string `json:"lastOutput"` // session_snapshot: last stage output (reopen)
|
||||
LastResponse string `json:"lastResponse"` // session_snapshot: last stage response (reopen)
|
||||
Content string `json:"content"`
|
||||
Diff *string `json:"diff"`
|
||||
SessionID string `json:"sessionId"`
|
||||
WorkflowID string `json:"workflowId"`
|
||||
StageID string `json:"stageId"`
|
||||
ToolName string `json:"toolName"`
|
||||
Role string `json:"role"`
|
||||
TurnID string `json:"turnId"`
|
||||
Reason string `json:"reason"`
|
||||
Summary string `json:"outputSummary"`
|
||||
Response string `json:"responseText"`
|
||||
Reasoning string `json:"reasoning"` // inference.completed: model thinking trace (collapsed view)
|
||||
LastOutput string `json:"lastOutput"` // session_snapshot: last stage output (reopen)
|
||||
LastResponse string `json:"lastResponse"` // session_snapshot: last stage response (reopen)
|
||||
Content string `json:"content"`
|
||||
Diff *string `json:"diff"`
|
||||
// tool.completed: file/dir path(s) the tool read, wrote, or listed.
|
||||
AffectedEntities []string `json:"affectedEntities"`
|
||||
Tier string `json:"tier"`
|
||||
Message string `json:"message"`
|
||||
RequestID string `json:"requestId"`
|
||||
ArtifactID string `json:"artifactId"`
|
||||
ProviderID string `json:"providerId"`
|
||||
Preview *string `json:"preview"`
|
||||
Disposition string `json:"disposition"`
|
||||
Outcome string `json:"outcome"` // approval.resolved: APPROVED | REJECTED | AUTO_APPROVED
|
||||
WorkspaceRoot string `json:"workspaceRoot"` // session.workspace_bound: the bound cwd
|
||||
// tool.started: pretty "key=value" call arguments (path=…, command="…").
|
||||
Params []string `json:"params"`
|
||||
Tier string `json:"tier"`
|
||||
Message string `json:"message"`
|
||||
RequestID string `json:"requestId"`
|
||||
ArtifactID string `json:"artifactId"`
|
||||
ProviderID string `json:"providerId"`
|
||||
Preview *string `json:"preview"`
|
||||
Disposition string `json:"disposition"`
|
||||
Outcome string `json:"outcome"` // approval.resolved: APPROVED | REJECTED | AUTO_APPROVED
|
||||
WorkspaceRoot string `json:"workspaceRoot"` // session.workspace_bound: the bound cwd
|
||||
Name string `json:"name"` // session.renamed: human title derived from intent
|
||||
|
||||
// plan.locked: the compiled phase-2 stage ids in execution order
|
||||
StageIDs []string `json:"stageIds"`
|
||||
@@ -134,6 +139,7 @@ type ServerMessage struct {
|
||||
PendingAppr []ApprovalDto `json:"pendingApprovals"`
|
||||
Tools []ToolRecordDto `json:"tools"`
|
||||
RecentEvents []EventEntryDto `json:"recentEvents"`
|
||||
Transcript []TranscriptRowDto `json:"transcript"`
|
||||
Stages []StageToolDecl `json:"stages"`
|
||||
Workflows []WorkflowDto `json:"workflows"`
|
||||
AssessedIssues []AssessedIssueDto `json:"issues"`
|
||||
@@ -387,6 +393,22 @@ type ToolRecordDto struct {
|
||||
Diff *string `json:"diff"`
|
||||
}
|
||||
|
||||
// TranscriptRowDto is one raw OUTPUT-transcript source row from a session_snapshot; the client
|
||||
// folds these into RouterEntry rows on reopen, applying its own diff/param/action formatting.
|
||||
type TranscriptRowDto struct {
|
||||
Kind string `json:"kind"`
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
ToolName string `json:"toolName"`
|
||||
Status string `json:"status"`
|
||||
Diff *string `json:"diff"`
|
||||
Params []string `json:"params"`
|
||||
Summary string `json:"summary"`
|
||||
AffectedEntities []string `json:"affectedEntities"`
|
||||
LatencyMs *int64 `json:"latencyMs"`
|
||||
TotalTokens *int `json:"totalTokens"`
|
||||
}
|
||||
|
||||
type WorkflowDto struct {
|
||||
WorkflowID string `json:"workflowId"`
|
||||
Description string `json:"description"`
|
||||
@@ -413,7 +435,7 @@ func (m ServerMessage) IsEventBearing() bool {
|
||||
TypeToolAssessed,
|
||||
TypeApprovalRequired, TypeApprovalResolved, TypeClarification,
|
||||
TypeWorkflowProposed,
|
||||
TypeWorkspaceBound, TypeArtifactCreated,
|
||||
TypeSessionRenamed, TypeWorkspaceBound, TypeArtifactCreated,
|
||||
TypeArtifactValid, TypePlanLocked,
|
||||
TypeRouterNarration, TypeReviewFindings:
|
||||
return true
|
||||
|
||||
Reference in New Issue
Block a user