Merge nucleic/olive-coral-civet-pvqv into dev
This commit is contained in:
@@ -170,13 +170,22 @@ struct ApprovalCardView: View {
|
|||||||
struct ExitPlanModeApprovalCardView: View {
|
struct ExitPlanModeApprovalCardView: View {
|
||||||
@EnvironmentObject var store: RemoteStore
|
@EnvironmentObject var store: RemoteStore
|
||||||
let approval: ApprovalRequest
|
let approval: ApprovalRequest
|
||||||
let plan: ExitPlanMode.Plan?
|
let plan: PlanReview.Plan?
|
||||||
var availableHeight: CGFloat = 0
|
var availableHeight: CGFloat = 0
|
||||||
|
|
||||||
@State private var revising = false
|
@State private var revising = false
|
||||||
@State private var feedback = ""
|
@State private var feedback = ""
|
||||||
|
|
||||||
private var canRespond: Bool { store.connectivity.isLive }
|
private var canRespond: Bool { store.connectivity.isLive }
|
||||||
|
private var isLegacyExitPlanMode: Bool { approval.toolName == ExitPlanMode.toolName }
|
||||||
|
private var rejectionReason: String {
|
||||||
|
isLegacyExitPlanMode ? ExitPlanMode.rejectionReason : PlanReview.rejectionReason
|
||||||
|
}
|
||||||
|
private func revisionReason(_ feedback: String) -> String? {
|
||||||
|
isLegacyExitPlanMode
|
||||||
|
? ExitPlanMode.revisionReason(feedback: feedback)
|
||||||
|
: PlanReview.revisionReason(feedback: feedback)
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 10) {
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
@@ -193,13 +202,13 @@ struct ExitPlanModeApprovalCardView: View {
|
|||||||
// Claude versions that don't include the plan Markdown in the permission payload
|
// Claude versions that don't include the plan Markdown in the permission payload
|
||||||
// still need the three-way review. State that plainly instead of degrading to the
|
// still need the three-way review. State that plainly instead of degrading to the
|
||||||
// generic permission card (which would incorrectly offer Allow Always).
|
// generic permission card (which would incorrectly offer Allow Always).
|
||||||
Text("Claude is ready to leave plan mode and begin implementation.")
|
Text("The agent is ready to begin implementation and is waiting for plan review.")
|
||||||
.font(.footnote).foregroundStyle(.secondary)
|
.font(.footnote).foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
if revising {
|
if revising {
|
||||||
VStack(alignment: .leading, spacing: 6) {
|
VStack(alignment: .leading, spacing: 6) {
|
||||||
Text("What should Claude change?")
|
Text("What should the agent change?")
|
||||||
.font(.footnote.weight(.semibold))
|
.font(.footnote.weight(.semibold))
|
||||||
TextField(
|
TextField(
|
||||||
"Describe the changes you want in the plan…",
|
"Describe the changes you want in the plan…",
|
||||||
@@ -219,13 +228,13 @@ struct ExitPlanModeApprovalCardView: View {
|
|||||||
Button("Send Revision") { submitRevision() }
|
Button("Send Revision") { submitRevision() }
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(.borderedProminent)
|
||||||
.disabled(!canRespond || ExitPlanMode.revisionReason(feedback: feedback) == nil)
|
.disabled(!canRespond || revisionReason(feedback) == nil)
|
||||||
.keyboardShortcut(.defaultAction)
|
.keyboardShortcut(.defaultAction)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
Button(role: .destructive) {
|
Button(role: .destructive) {
|
||||||
store.respond(approval, .deny(reason: ExitPlanMode.rejectionReason))
|
store.respond(approval, .deny(reason: rejectionReason))
|
||||||
} label: {
|
} label: {
|
||||||
Text("Deny").frame(maxWidth: .infinity)
|
Text("Deny").frame(maxWidth: .infinity)
|
||||||
}
|
}
|
||||||
@@ -263,7 +272,7 @@ struct ExitPlanModeApprovalCardView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func submitRevision() {
|
private func submitRevision() {
|
||||||
guard let reason = ExitPlanMode.revisionReason(feedback: feedback) else { return }
|
guard let reason = revisionReason(feedback) else { return }
|
||||||
store.respond(approval, .deny(reason: reason))
|
store.respond(approval, .deny(reason: reason))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -638,10 +638,10 @@ struct SessionDetailView: View {
|
|||||||
{
|
{
|
||||||
AskUserQuestionCardView(
|
AskUserQuestionCardView(
|
||||||
approval: approval, questions: questions, availableHeight: availableHeight)
|
approval: approval, questions: questions, availableHeight: availableHeight)
|
||||||
} else if approval.toolName == ExitPlanMode.toolName {
|
} else if PlanReview.isPlanTool(approval.toolName) {
|
||||||
ExitPlanModeApprovalCardView(
|
ExitPlanModeApprovalCardView(
|
||||||
approval: approval,
|
approval: approval,
|
||||||
plan: ExitPlanMode.plan(from: approval.input),
|
plan: PlanReview.plan(from: approval.input),
|
||||||
availableHeight: availableHeight)
|
availableHeight: availableHeight)
|
||||||
} else {
|
} else {
|
||||||
ApprovalCardView(approval: approval, availableHeight: availableHeight)
|
ApprovalCardView(approval: approval, availableHeight: availableHeight)
|
||||||
@@ -860,7 +860,7 @@ struct SessionDetailView: View {
|
|||||||
if let approval = store.openApprovals.first {
|
if let approval = store.openApprovals.first {
|
||||||
switch approval.toolName {
|
switch approval.toolName {
|
||||||
case AskUserQuestion.toolName: return "Waiting for answers…"
|
case AskUserQuestion.toolName: return "Waiting for answers…"
|
||||||
case ExitPlanMode.toolName: return "Waiting for plan review…"
|
case ExitPlanMode.toolName, PlanReview.toolName: return "Waiting for plan review…"
|
||||||
default: return "Waiting for approval…"
|
default: return "Waiting for approval…"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -582,7 +582,8 @@ enum ToolGlyph {
|
|||||||
case "Task", "Agent", ToolGroup.orchestraSubagentToolName: return "person.2"
|
case "Task", "Agent", ToolGroup.orchestraSubagentToolName: return "person.2"
|
||||||
case "TodoWrite": return "checklist"
|
case "TodoWrite": return "checklist"
|
||||||
case "AskUserQuestion": return "questionmark.bubble"
|
case "AskUserQuestion": return "questionmark.bubble"
|
||||||
case ExitPlanMode.toolName: return "list.bullet.clipboard"
|
case ExitPlanMode.toolName, PlanReview.toolName, PlanReview.qualifiedToolName:
|
||||||
|
return "list.bullet.clipboard"
|
||||||
case HostCommandSummary.hostExecToolName: return "desktopcomputer"
|
case HostCommandSummary.hostExecToolName: return "desktopcomputer"
|
||||||
// The VM/container tools each get their own glyph — a screen for computer-use, a box for
|
// The VM/container tools each get their own glyph — a screen for computer-use, a box for
|
||||||
// a container — so they don't all collapse into the generic wrench.
|
// a container — so they don't all collapse into the generic wrench.
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ struct ToolGroup: Equatable {
|
|||||||
name == "Task" || name == "Agent" || name == Self.orchestraSubagentToolName
|
name == "Task" || name == "Agent" || name == Self.orchestraSubagentToolName
|
||||||
}
|
}
|
||||||
var isAskUserQuestion: Bool { name == "AskUserQuestion" }
|
var isAskUserQuestion: Bool { name == "AskUserQuestion" }
|
||||||
var isExitPlanMode: Bool { name == ExitPlanMode.toolName }
|
var isExitPlanMode: Bool { PlanReview.isPlanTool(name) }
|
||||||
|
|
||||||
/// The literal shell command a `Bash` call ran, if any (nil for every other tool). Used to
|
/// The literal shell command a `Bash` call ran, if any (nil for every other tool). Used to
|
||||||
/// detect a git-commit pipeline that should read as a structured commit card at the row level,
|
/// detect a git-commit pipeline that should read as a structured commit card at the row level,
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ struct TranscriptRow: View {
|
|||||||
/// Nil for any other tool (which keeps the normal collapsible card) and for a call with no plan
|
/// Nil for any other tool (which keeps the normal collapsible card) and for a call with no plan
|
||||||
/// text. The plan is on the call input, so it shows whether or not the user has approved it yet.
|
/// text. The plan is on the call input, so it shows whether or not the user has approved it yet.
|
||||||
private func planCard(_ group: ToolGroup) -> ExitPlanModeCard? {
|
private func planCard(_ group: ToolGroup) -> ExitPlanModeCard? {
|
||||||
guard group.isExitPlanMode, let plan = ExitPlanMode.plan(from: group.input) else { return nil }
|
guard group.isExitPlanMode, let plan = PlanReview.plan(from: group.input) else { return nil }
|
||||||
return ExitPlanModeCard(plan: plan)
|
return ExitPlanModeCard(plan: plan)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user