Merge nucleic/zesty-jade-weasel-yb6i into dev

This commit is contained in:
2026-08-07 18:03:28 -07:00
parent 3174ced502
commit 20a70d5770
@@ -41,6 +41,10 @@ enum SandboxToolDisplay {
case "linux_container": "Linux container" case "linux_container": "Linux container"
case "nucleic_supervise": "Supervising subagents" case "nucleic_supervise": "Supervising subagents"
case AskUserQuestion.nucleicToolName: "Question for you" case AskUserQuestion.nucleicToolName: "Question for you"
case "nucleic_knowledge_search": "Search project knowledge"
case "nucleic_knowledge_get": "Read project knowledge"
case "nucleic_knowledge_add": "Add project knowledge"
case "nucleic_knowledge_edit": "Edit project knowledge"
default: nil default: nil
} }
} }
@@ -71,6 +75,10 @@ enum SandboxToolDisplay {
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 subagents…"
case AskUserQuestion.nucleicToolName: "Waiting on your answer…" case AskUserQuestion.nucleicToolName: "Waiting on your answer…"
case "nucleic_knowledge_search": "Searching project knowledge…"
case "nucleic_knowledge_get": "Reading project knowledge…"
case "nucleic_knowledge_add": "Adding to project knowledge…"
case "nucleic_knowledge_edit": "Editing project knowledge…"
default: nil default: nil
} }
} }
@@ -90,6 +98,10 @@ enum SandboxToolDisplay {
case "mac_vm_control", "linux_vm_control": "power" case "mac_vm_control", "linux_vm_control": "power"
case "nucleic_supervise": "eye" case "nucleic_supervise": "eye"
case AskUserQuestion.nucleicToolName: "questionmark.bubble" case AskUserQuestion.nucleicToolName: "questionmark.bubble"
case "nucleic_knowledge_search": "magnifyingglass"
case "nucleic_knowledge_get": "point.3.connected.trianglepath.dotted"
case "nucleic_knowledge_add": "note.text.badge.plus"
case "nucleic_knowledge_edit": "square.and.pencil"
default: nil default: nil
} }
} }
@@ -129,6 +141,16 @@ enum SandboxToolDisplay {
let first = questions.first else { return nil } let first = questions.first else { return nil }
let suffix = questions.count > 1 ? " +\(questions.count - 1) more" : "" let suffix = questions.count > 1 ? " +\(questions.count - 1) more" : ""
return singleLine(first.question) + suffix return singleLine(first.question) + suffix
// An empty query isn't a degenerate search — it's how the overview is asked for.
case "nucleic_knowledge_search":
return string(input, "query").map(singleLine) ?? "overview"
case "nucleic_knowledge_get":
return entityNames(input)
// Writes are shaped as arrays, so the row is what they add rather than one element of them.
case "nucleic_knowledge_add":
return addSummary(input)
case "nucleic_knowledge_edit":
return editSummary(input)
default: default:
return nil return nil
} }
@@ -196,6 +218,46 @@ enum SandboxToolDisplay {
return "\(op) — \(subject)" return "\(op) — \(subject)"
} }
/// The entities a knowledge fetch asked for, joined — the names are the call. A lone `name` is
/// accepted too, since fetching one entity is the common case.
private static func entityNames(_ input: JSONValue) -> String? {
let names = (input["names"]?.arrayValue ?? [])
.compactMap { $0.stringValue ?? $0["name"]?.stringValue }
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
.filter { !$0.isEmpty }
guard !names.isEmpty else { return string(input, "name") }
return clip(names.joined(separator: ", "), limit: 60)
}
/// What a knowledge write puts into the graph — "2 entities, 3 relations" — counted per bucket.
/// Observations carried inside an entity count toward the observation total, and so do anchors —
/// the schema only ever nests those, so the top-level bucket alone would always read as zero.
private static func addSummary(_ input: JSONValue) -> String? {
let entities = input["entities"]?.arrayValue ?? []
let nested = entities.reduce(0) { $0 + ($1["observations"]?.arrayValue?.count ?? 0) }
let nestedAnchors = entities.reduce(0) { $0 + ($1["anchors"]?.arrayValue?.count ?? 0) }
let buckets: [(singular: String, plural: String, count: Int)] = [
("entity", "entities", entities.count),
("observation", "observations",
(input["observations"]?.arrayValue?.count ?? 0) + nested),
("relation", "relations", input["relations"]?.arrayValue?.count ?? 0),
("anchor", "anchors",
(input["anchors"]?.arrayValue?.count ?? 0) + nestedAnchors),
]
let parts = buckets.filter { $0.count > 0 }
.map { "\($0.count) \($0.count == 1 ? $0.singular : $0.plural)" }
return parts.isEmpty ? nil : parts.joined(separator: ", ")
}
/// A knowledge edit as how much it changes — "3 operations" — summed across its buckets, since
/// how many changes land says more in a one-line row than which bucket they came from.
private static func editSummary(_ input: JSONValue) -> String? {
let operations = (input.objectValue ?? [:]).values
.reduce(0) { $0 + ($1.arrayValue?.count ?? 0) }
guard operations > 0 else { return nil }
return "\(operations) operation\(operations == 1 ? "" : "s")"
}
/// A VM lifecycle op as a phrase, so "status" doesn't read as a state label. /// A VM lifecycle op as a phrase, so "status" doesn't read as a state label.
private static func controlOpPhrase(_ op: String) -> String { private static func controlOpPhrase(_ op: String) -> String {
switch op { switch op {