Merge nucleic/upbeat-ember-urchin-80xw into dev

This commit is contained in:
2026-08-07 19:24:44 -07:00
parent 20a70d5770
commit ff965ba977
7 changed files with 75 additions and 56 deletions
@@ -86,7 +86,7 @@ extension RemoteStore {
} }
/// Turn Orchestra on for an open chat. It rides the existing effort sentinel rather than a /// Turn Orchestra on for an open chat. It rides the existing effort sentinel rather than a
/// stop of its own (the host resolves the sentinel to xhigh plus standing subagent consent), /// stop of its own (the host resolves the sentinel to xhigh plus standing worker consent),
/// so this is the ordinary effort verb — which also means an older host honors it. /// so this is the ordinary effort verb — which also means an older host honors it.
func setSessionOrchestra(_ id: SessionID) { func setSessionOrchestra(_ id: SessionID) {
setSessionEffort(id, modelCatalog.orchestraSentinelOrFallback) setSessionEffort(id, modelCatalog.orchestraSentinelOrFallback)
@@ -169,7 +169,7 @@ extension RemoteStore {
defaultLevel: 3, defaultLevel: 3,
orchestraDisplayName: "Orchestra", orchestraDisplayName: "Orchestra",
orchestraBlurb: orchestraBlurb:
"Max, plus managed subagents: the work fans out to parallel workers and one " "Max, plus managed workers: the work fans out in parallel and one "
+ "supervisor pulls the results together. Nucleic Control projects only.", + "supervisor pulls the results together. Nucleic Control projects only.",
orchestraAvailable: true) orchestraAvailable: true)
} }
@@ -527,7 +527,7 @@ struct IntelligenceRail: View {
+ "thorough results and cost more. Swipe up or down to change it." + "thorough results and cost more. Swipe up or down to change it."
if orchestraAvailable { if orchestraAvailable {
hint += " The stop past \(catalog.displayName(forLevel: maxRawLevel)) is " hint += " The stop past \(catalog.displayName(forLevel: maxRawLevel)) is "
+ "\(catalog.orchestraDisplayName), which fans work out to parallel subagents." + "\(catalog.orchestraDisplayName), which fans work out to parallel workers."
} }
return hint return hint
} }
@@ -47,7 +47,7 @@ struct IntelligenceRouteLine: View {
/// which the rail above already shows. Orchestra states its mode rather than a pair — the /// which the rail above already shows. Orchestra states its mode rather than a pair — the
/// supervisor model isn't the interesting fact there. /// supervisor model isn't the interesting fact there.
private var summary: String { private var summary: String {
if orchestra { return "Parallel subagents" } if orchestra { return "Parallel workers" }
if let unavailableReason { return unavailableReason.localizedCapitalized } if let unavailableReason { return unavailableReason.localizedCapitalized }
guard let model, let effort else { return "" } guard let model, let effort else { return "" }
return "\(modelCatalog.displayName(model)) · " return "\(modelCatalog.displayName(model)) · "
@@ -39,7 +39,7 @@ enum SandboxToolDisplay {
case "linux_vm_exec", "linux_vm_control": "Linux VM" case "linux_vm_exec", "linux_vm_control": "Linux VM"
case "linux_vm_computer", "linux_vm_computer_batch": "Linux VM screen" case "linux_vm_computer", "linux_vm_computer_batch": "Linux VM screen"
case "linux_container": "Linux container" case "linux_container": "Linux container"
case "nucleic_supervise": "Supervising subagents" case "nucleic_supervise": "Supervising workers"
case AskUserQuestion.nucleicToolName: "Question for you" case AskUserQuestion.nucleicToolName: "Question for you"
case "nucleic_knowledge_search": "Search project knowledge" case "nucleic_knowledge_search": "Search project knowledge"
case "nucleic_knowledge_get": "Read project knowledge" case "nucleic_knowledge_get": "Read project knowledge"
@@ -73,7 +73,7 @@ enum SandboxToolDisplay {
case "mac_vm_request_operator": "Waiting for you to drive the VM…" case "mac_vm_request_operator": "Waiting for you to drive the VM…"
case "mac_vm_control": "Managing the macOS VM…" case "mac_vm_control": "Managing the macOS VM…"
case "linux_vm_control": "Managing the Linux VM…" case "linux_vm_control": "Managing the Linux VM…"
case "nucleic_supervise": "Waiting on subagents…" case "nucleic_supervise": "Waiting on workers…"
case AskUserQuestion.nucleicToolName: "Waiting on your answer…" case AskUserQuestion.nucleicToolName: "Waiting on your answer…"
case "nucleic_knowledge_search": "Searching project knowledge…" case "nucleic_knowledge_search": "Searching project knowledge…"
case "nucleic_knowledge_get": "Reading project knowledge…" case "nucleic_knowledge_get": "Reading project knowledge…"
@@ -29,7 +29,7 @@ enum ToolRailMetrics {
} }
/// The hairline a run of tool lines hangs off. Draws no fill and no border: nested inside a gold /// The hairline a run of tool lines hangs off. Draws no fill and no border: nested inside a gold
/// subagent card it adds a rule, not another box. /// delegation card it adds a rule, not another box.
struct ToolRail<Content: View>: View { struct ToolRail<Content: View>: View {
/// Overrides the hairline colour — the danger tint when a call in the run failed. /// Overrides the hairline colour — the danger tint when a call in the run failed.
var tint: Color? = nil var tint: Color? = nil
@@ -228,22 +228,24 @@ struct ToolBlockCard: View {
} }
} }
// MARK: - Subagent & multi-agent cards // MARK: - Delegation & multi-agent cards
// //
// Claude Code spawns subagents through the `Task` tool. Nucleic surfaces that delegated work in // Two things land here: Claude Code's own subagents, spawned through the `Task` tool, and
// the orchestra gold so it reads distinctly from the agent's own tool calls: a lone `Task` // Nucleic's managed workers, spawned through `nucleic_worker`. Both are delegated work, so both
// becomes a `SubagentCard`, and a parallel wave of them becomes an `OrchestrationCard`. Both // get the orchestra gold that sets them apart from the agent's own tool calls: a lone spawn
// build from the projection's nested `children` (a subagent's inner Read/Bash/Edit/thinking), // becomes a `SubagentCard` (which names itself Worker or Subagent accordingly), and a parallel
// mirroring the Mac at phone fidelity. // wave becomes an `OrchestrationCard`. Both build from the projection's nested `children` (the
// delegate's inner Read/Bash/Edit/thinking), mirroring the Mac at phone fidelity.
/// The lifecycle state of a spawned subagent, read from its `Task` group: not finished → still /// The lifecycle state of a spawned agent, read from its group: not finished → still working;
/// working; an error result → failed; otherwise → done. /// an error result → failed; otherwise → done. Shared by the worker and `Task` cards, so the
enum SubagentRunState { /// two speak one status vocabulary.
enum WorkerRunState {
case running, done, failed case running, done, failed
init(group: ToolGroup) { init(group: ToolGroup) {
if !group.finished { self = .running } if !group.finished { self = .running }
// A `Task` subagent surfaces failure as an error result. A `nucleic_subagent` worker // A `Task` subagent surfaces failure as an error result. A `nucleic_worker` worker
// instead returns a *non-error* result whose JSON envelope carries `{"ok": false}` (or // instead returns a *non-error* result whose JSON envelope carries `{"ok": false}` (or
// `{"denied": true}`) in-band, so read that too — otherwise a failed worker reads "Done". // `{"denied": true}`) in-band, so read that too — otherwise a failed worker reads "Done".
else if group.isError || Subagent.workerFailed(group) { self = .failed } else if group.isError || Subagent.workerFailed(group) { self = .failed }
@@ -276,7 +278,7 @@ enum Subagent {
return (raw?.isEmpty == false) ? raw : nil return (raw?.isEmpty == false) ? raw : nil
} }
/// The one-line task the parent handed the subagent: a `nucleic_subagent` worker's `task` /// The one-line task the parent handed the subagent: a `nucleic_worker` worker's `task`
/// label, or a `Task` subagent's `description`, falling back to the full `prompt`, then a /// label, or a `Task` subagent's `description`, falling back to the full `prompt`, then a
/// generic label. /// generic label.
static func taskLabel(_ group: ToolGroup) -> String { static func taskLabel(_ group: ToolGroup) -> String {
@@ -305,7 +307,7 @@ enum Subagent {
/// The subagent's returned report, or nil while it's still working / reported nothing. /// The subagent's returned report, or nil while it's still working / reported nothing.
static func report(_ group: ToolGroup) -> String? { static func report(_ group: ToolGroup) -> String? {
guard let result = group.result else { return nil } guard let result = group.result else { return nil }
// A `nucleic_subagent` worker returns a JSON envelope — surface its actual `output` (or // A `nucleic_worker` worker returns a JSON envelope — surface its actual `output` (or
// failure `message`) rather than a one-line gist of the raw `{ok,session_id,…}` object. A // failure `message`) rather than a one-line gist of the raw `{ok,session_id,…}` object. A
// `Task` subagent returns prose, which falls through unchanged — via `resultText`, so a // `Task` subagent returns prose, which falls through unchanged — via `resultText`, so a
// report delivered as an MCP content array reads as the report and not as "[1 items]". // report delivered as an MCP content array reads as the report and not as "[1 items]".
@@ -314,7 +316,7 @@ enum Subagent {
return text.isEmpty ? nil : text return text.isEmpty ? nil : text
} }
/// Whether a `nucleic_subagent` worker result reports failure — a `{"ok": false}` completion /// Whether a `nucleic_worker` worker result reports failure — a `{"ok": false}` completion
/// or a `{"denied": true}` refusal, both delivered as non-error tool results. False for any /// or a `{"denied": true}` refusal, both delivered as non-error tool results. False for any
/// other tool (a `Task` subagent, whose failure is an error result handled separately). /// other tool (a `Task` subagent, whose failure is an error result handled separately).
static func workerFailed(_ group: ToolGroup) -> Bool { static func workerFailed(_ group: ToolGroup) -> Bool {
@@ -325,7 +327,7 @@ enum Subagent {
return false return false
} }
/// The human-facing text of a `nucleic_subagent` worker envelope — the worker's `output` on /// The human-facing text of a `nucleic_worker` worker envelope — the worker's `output` on
/// success, or its `message` on failure/denial — or nil when `text` isn't such an envelope /// success, or its `message` on failure/denial — or nil when `text` isn't such an envelope
/// (so a `Task` subagent's prose report falls through unchanged). /// (so a `Task` subagent's prose report falls through unchanged).
private static func workerText(_ text: String) -> String? { private static func workerText(_ text: String) -> String? {
@@ -338,7 +340,7 @@ enum Subagent {
: (envelope["message"] as? String ?? "") : (envelope["message"] as? String ?? "")
} }
/// Parses a `nucleic_subagent` result string into its JSON envelope, gated on the envelope's /// Parses a `nucleic_worker` result string into its JSON envelope, gated on the envelope's
/// signature keys (`ok` / `denied`) so an arbitrary JSON-shaped tool result isn't mistaken for /// signature keys (`ok` / `denied`) so an arbitrary JSON-shaped tool result isn't mistaken for
/// one. Nil for anything that isn't a worker envelope. /// one. Nil for anything that isn't a worker envelope.
private static func workerEnvelope(_ text: String) -> [String: Any]? { private static func workerEnvelope(_ text: String) -> [String: Any]? {
@@ -350,10 +352,10 @@ enum Subagent {
} }
} }
/// A small leading status glyph for a subagent — a gold spinner while it works, a green check or /// A small leading status glyph for a worker — a gold spinner while it works, a green check or
/// red cross once it settles. /// red cross once it settles.
struct SubagentStatusGlyph: View { struct WorkerStatusGlyph: View {
let state: SubagentRunState let state: WorkerRunState
var body: some View { var body: some View {
Group { Group {
switch state { switch state {
@@ -366,12 +368,12 @@ struct SubagentStatusGlyph: View {
} }
} }
/// A status pill (spinner / check / cross + word) for a subagent card header. /// A status pill (spinner / check / cross + word) for the header of a worker card.
struct SubagentStatusChip: View { struct WorkerStatusChip: View {
let state: SubagentRunState let state: WorkerRunState
var body: some View { var body: some View {
HStack(spacing: 4) { HStack(spacing: 4) {
SubagentStatusGlyph(state: state).frame(width: 14) WorkerStatusGlyph(state: state).frame(width: 14)
Text(state.label) Text(state.label)
} }
.font(.caption2.weight(.medium)) .font(.caption2.weight(.medium))
@@ -381,14 +383,16 @@ struct SubagentStatusChip: View {
} }
} }
/// One delegated subagent (`Task`), as a gold card: its type and the task it was given, a live /// One delegated agent as a gold card — a Nucleic-managed worker (`nucleic_worker`) or a
/// Working / Done / Failed status, and — on expand — its nested activity stream (or, for a /// provider-native subagent (`Task`), which the header names accordingly: its type and the task
/// replayed transcript with none, its returned report). Mirrors the Mac's `SubagentCard`. /// it was given, a live Working / Done / Failed status, and — on expand — its nested activity
/// stream (or, for a replayed transcript with none, its returned report). Mirrors the Mac's
/// `SubagentCard`.
struct SubagentCard: View { struct SubagentCard: View {
let group: ToolGroup let group: ToolGroup
@State private var expanded = false @State private var expanded = false
private var state: SubagentRunState { SubagentRunState(group: group) } private var state: WorkerRunState { WorkerRunState(group: group) }
private var report: String? { Subagent.report(group) } private var report: String? { Subagent.report(group) }
private var hasDetail: Bool { !group.children.isEmpty || report != nil } private var hasDetail: Bool { !group.children.isEmpty || report != nil }
@@ -415,11 +419,11 @@ struct SubagentCard: View {
HStack(alignment: .firstTextBaseline, spacing: 10) { HStack(alignment: .firstTextBaseline, spacing: 10) {
Image(systemName: "person.2.fill") Image(systemName: "person.2.fill")
.font(.callout).foregroundStyle(Palette.orchestra).frame(width: 18) .font(.callout).foregroundStyle(Palette.orchestra).frame(width: 18)
(Text("Subagent") (Text(group.isManagedWorker ? "Worker" : "Subagent")
+ (Subagent.type(group).map { Text(" · \($0)").foregroundColor(.secondary) } ?? Text(""))) + (Subagent.type(group).map { Text(" · \($0)").foregroundColor(.secondary) } ?? Text("")))
.font(.callout.weight(.semibold)) .font(.callout.weight(.semibold))
Spacer(minLength: 6) Spacer(minLength: 6)
SubagentStatusChip(state: state) WorkerStatusChip(state: state)
if hasDetail { if hasDetail {
Image(systemName: expanded ? "chevron.up" : "chevron.down") Image(systemName: expanded ? "chevron.up" : "chevron.down")
.font(.caption2).foregroundStyle(.secondary) .font(.caption2).foregroundStyle(.secondary)
@@ -434,15 +438,20 @@ struct SubagentCard: View {
} }
} }
/// A multi-agent fan-out: a wave of two or more subagents (`Task` calls) spawned together. The /// A multi-agent fan-out: a wave of two or more delegates spawned together. The header sums up
/// header sums up the wave (count + overall status); each subagent is its own row that expands /// the wave (count + overall status); each one is its own row that expands independently to
/// independently to reveal its activity stream and report. Mirrors the Mac's `OrchestrationCard`. /// reveal its activity stream and report. Mirrors the Mac's `OrchestrationCard`.
struct OrchestrationCard: View { struct OrchestrationCard: View {
let groups: [ToolGroup] let groups: [ToolGroup]
/// Whether every call in the wave is a Nucleic-managed worker spawn, which is what entitles
/// the card to say "Workers". A mixed wave takes the broader word.
private var allManaged: Bool { groups.allSatisfy(\.isManagedWorker) }
private var noun: String { allManaged ? "worker" : "subagent" }
/// Overall wave status: failed if any failed, else running if any is still working, else done. /// Overall wave status: failed if any failed, else running if any is still working, else done.
private var overall: SubagentRunState { private var overall: WorkerRunState {
let states = groups.map { SubagentRunState(group: $0) } let states = groups.map { WorkerRunState(group: $0) }
if states.contains(where: { if case .failed = $0 { true } else { false } }) { return .failed } if states.contains(where: { if case .failed = $0 { true } else { false } }) { return .failed }
if states.contains(where: { if case .running = $0 { true } else { false } }) { return .running } if states.contains(where: { if case .running = $0 { true } else { false } }) { return .running }
return .done return .done
@@ -467,25 +476,26 @@ struct OrchestrationCard: View {
Image(systemName: "rectangle.3.group.fill") Image(systemName: "rectangle.3.group.fill")
.font(.callout).foregroundStyle(Palette.orchestra).frame(width: 18) .font(.callout).foregroundStyle(Palette.orchestra).frame(width: 18)
VStack(alignment: .leading, spacing: 1) { VStack(alignment: .leading, spacing: 1) {
Text("Multiple Subagents").font(.callout.weight(.semibold)) Text(allManaged ? "Multiple Workers" : "Multiple Subagents")
Text("\(groups.count) subagents · \(doneCount) done") .font(.callout.weight(.semibold))
Text("\(groups.count) \(noun)s · \(doneCount) done")
.font(.caption2).foregroundStyle(.secondary) .font(.caption2).foregroundStyle(.secondary)
} }
Spacer(minLength: 6) Spacer(minLength: 6)
SubagentStatusChip(state: overall) WorkerStatusChip(state: overall)
} }
.padding(.horizontal, 12).padding(.vertical, 10) .padding(.horizontal, 12).padding(.vertical, 10)
} }
} }
/// One subagent within a fan-out: a one-line status header that expands on tap to reveal that /// One agent within a fan-out: a one-line status header that expands on tap to reveal that
/// agent's own activity stream — and, failing that, its returned report. Each row tracks its own /// agent's own activity stream — and, failing that, its returned report. Each row tracks its own
/// expansion so agents open independently. /// expansion so agents open independently.
private struct OrchestrationAgentRow: View { private struct OrchestrationAgentRow: View {
let group: ToolGroup let group: ToolGroup
@State private var expanded = false @State private var expanded = false
private var state: SubagentRunState { SubagentRunState(group: group) } private var state: WorkerRunState { WorkerRunState(group: group) }
private var report: String? { Subagent.report(group) } private var report: String? { Subagent.report(group) }
private var hasDetail: Bool { !group.children.isEmpty || report != nil } private var hasDetail: Bool { !group.children.isEmpty || report != nil }
@@ -505,7 +515,7 @@ private struct OrchestrationAgentRow: View {
private var header: some View { private var header: some View {
HStack(alignment: .firstTextBaseline, spacing: 9) { HStack(alignment: .firstTextBaseline, spacing: 9) {
SubagentStatusGlyph(state: state) WorkerStatusGlyph(state: state)
Text(Subagent.headline(group)) Text(Subagent.headline(group))
.font(.caption).lineLimit(1).truncationMode(.tail) .font(.caption).lineLimit(1).truncationMode(.tail)
Spacer(minLength: 6) Spacer(minLength: 6)
@@ -518,8 +528,8 @@ private struct OrchestrationAgentRow: View {
} }
} }
/// A subagent's nested activity — the calls, thinking, and prose it produced — rendered with the /// A delegate's nested activity — the calls, thinking, and prose it produced — rendered with the
/// same row views the main transcript uses, recursing into nested subagents' own cards. Falls /// same row views the main transcript uses, recursing into nested delegates' own cards. Falls
/// back to the returned report when there's no captured inner activity (a replayed transcript). /// back to the returned report when there's no captured inner activity (a replayed transcript).
private struct SubagentActivityStream: View { private struct SubagentActivityStream: View {
let children: [TranscriptItem] let children: [TranscriptItem]
@@ -784,7 +794,8 @@ enum ToolGlyph {
case "Edit", "Write", "MultiEdit", "NotebookEdit": return "pencil" case "Edit", "Write", "MultiEdit", "NotebookEdit": return "pencil"
case "Grep", "Glob", "Search": return "magnifyingglass" case "Grep", "Glob", "Search": return "magnifyingglass"
case "WebFetch", "WebSearch": return "globe" case "WebFetch", "WebSearch": return "globe"
case "Task", "Agent", ToolGroup.orchestraSubagentToolName: return "person.2" case "Task", "Agent", ToolGroup.orchestraWorkerToolName,
ToolGroup.legacyOrchestraWorkerToolName: return "person.2"
case "TodoWrite": return "checklist" case "TodoWrite": return "checklist"
case ExitPlanMode.toolName, PlanReview.toolName, PlanReview.qualifiedToolName: case ExitPlanMode.toolName, PlanReview.toolName, PlanReview.qualifiedToolName:
return "list.bullet.clipboard" return "list.bullet.clipboard"
@@ -107,18 +107,26 @@ struct ToolGroup: Equatable {
struct FilePatch: Equatable { let path: String; let change: FileChange.ChangeKind } struct FilePatch: Equatable { let path: String; let change: FileChange.ChangeKind }
/// Nucleic's own Orchestra worker spawn — the qualified `nucleic_subagent` MCP wire name. /// Nucleic's own Orchestra worker spawn — the qualified `nucleic_worker` MCP wire name.
/// Unlike a `Task` subagent, a worker runs as its own observed session, so it carries no /// Unlike a `Task` subagent, a worker runs as its own observed session, so it carries no
/// nested `children` here; its card shows the final output it returned instead. /// nested `children` here; its card shows the final output it returned instead.
static let orchestraSubagentToolName = "mcp__nucleic__nucleic_subagent" static let orchestraWorkerToolName = "mcp__nucleic__nucleic_worker"
/// The pre-rename wire name for the same spawn, still matched so worker cards in transcripts
/// recorded before the rename keep their gold card instead of falling back to a generic row.
static let legacyOrchestraWorkerToolName = "mcp__nucleic__nucleic_subagent"
/// The supervisor's blocking worker-event drain. Holder-level Orchestra lock notes that have /// The supervisor's blocking worker-event drain. Holder-level Orchestra lock notes that have
/// no edit card in the parent transcript fold onto this card. /// no edit card in the parent transcript fold onto this card.
static let orchestraSuperviseToolName = "mcp__nucleic__nucleic_supervise" static let orchestraSuperviseToolName = "mcp__nucleic__nucleic_supervise"
/// Subagent orchestration — a `Task`/`Agent` spawn or a `nucleic_subagent` worker — gets the /// Whether this call is a Nucleic-managed worker spawn, under either wire name.
/// gold card treatment, like the Mac. var isManagedWorker: Bool {
name == Self.orchestraWorkerToolName || name == Self.legacyOrchestraWorkerToolName
}
/// Delegation — a `Task`/`Agent` subagent spawn or a `nucleic_worker` worker — gets the gold
/// card treatment, like the Mac.
var isOrchestration: Bool { var isOrchestration: Bool {
name == "Task" || name == "Agent" || name == Self.orchestraSubagentToolName name == "Task" || name == "Agent" || isManagedWorker
} }
var isAskUserQuestion: Bool { AskUserQuestion.matches(toolName: name) } var isAskUserQuestion: Bool { AskUserQuestion.matches(toolName: name) }
var isExitPlanMode: Bool { PlanReview.isPlanTool(name) } var isExitPlanMode: Bool { PlanReview.isPlanTool(name) }
@@ -37,7 +37,7 @@ struct TranscriptRow: View {
case .tool(let group): case .tool(let group):
// A completed AskUserQuestion reads as a pretty "You answered" Q&A card; a git-commit // A completed AskUserQuestion reads as a pretty "You answered" Q&A card; a git-commit
// pipeline as a structured commit card; a host_exec call as a host card (purpose + // pipeline as a structured commit card; a host_exec call as a host card (purpose +
// command + breakdown); a lone subagent spawn as its own gold subagent card; every // command + breakdown); a lone worker or subagent spawn as its own gold delegation card; every
// other tool — and an as-yet-unanswered question — is a plain collapsible card. Mirrors // other tool — and an as-yet-unanswered question — is a plain collapsible card. Mirrors
// the Mac's `toolRow` routing so each family reads the same on both platforms. // the Mac's `toolRow` routing so each family reads the same on both platforms.
if group.isOrchestration { if group.isOrchestration {
@@ -54,7 +54,7 @@ struct TranscriptRow: View {
ToolCallCard(group: group) ToolCallCard(group: group)
} }
case .toolBlock(let groups): case .toolBlock(let groups):
// A run made entirely of subagent spawns is a multi-agent fan-out — the gold // A run made entirely of delegation spawns is a multi-agent fan-out — the gold
// orchestration card; any other run is one contiguous tool block. // orchestration card; any other run is one contiguous tool block.
if groups.allSatisfy(\.isOrchestration) { if groups.allSatisfy(\.isOrchestration) {
OrchestrationCard(groups: groups) OrchestrationCard(groups: groups)