Read App Intents Documentation

Nucleic-Session: 56C5F781-4444-466B-91CE-A7734A8E117F
Co-authored-by: Nucleic <[email protected]>
This commit is contained in:
2026-07-06 14:44:37 -07:00
co-authored by Nucleic
parent 8218d6042e
commit 8f0e721674
@@ -0,0 +1,101 @@
import AppIntents
import NucleicProtocol
// MARK: - Open a session (deep-link intent, §4.4)
/// Open a specific session in the app. Reuses the existing `nucleic://session/<id>` route via
/// `RemoteStore.route(to:)` — so a Spotlight result, a Siri phrase, or a Shortcut jumps straight
/// into the waiting session. `openAppWhenRun` foregrounds the app (this is a navigation action).
struct OpenSessionIntent: AppIntent {
static let title: LocalizedStringResource = "Open Session"
static let description = IntentDescription("Open a Nucleic agent session.")
static let openAppWhenRun = true
@Parameter(title: "Session")
var session: SessionEntity
init() {}
init(session: SessionEntity) { self.session = session }
@MainActor
func perform() async throws -> some IntentResult {
RemoteStore.shared.route(to: SessionID(rawValue: session.id))
return .result()
}
static var parameterSummary: some ParameterSummary {
Summary("Open \(\.$session)")
}
}
// MARK: - Unblock with a follow-up (§4.2, approve-scope)
/// Send a follow-up prompt to a paused agent — the "type the next step to unblock it" flow that
/// stays at `approve` scope (docs/UX_IOS §2). Runs in the background over the app's live channel
/// (no foregrounding); if the Mac isn't reachable it fails clean with a spoken error (§3.1).
struct SendFollowUpIntent: AppIntent {
static let title: LocalizedStringResource = "Send Follow-Up"
static let description = IntentDescription(
"Send a follow-up prompt to a Nucleic agent to unblock or steer it.")
@Parameter(title: "Session")
var session: SessionEntity
@Parameter(title: "Message", requestValueDialog: "What should the agent do next?")
var text: String
init() {}
init(session: SessionEntity, text: String) {
self.session = session
self.text = text
}
@MainActor
func perform() async throws -> some IntentResult & ProvidesDialog {
let store = RemoteStore.shared
guard store.isPaired else { throw IntentError.notPaired }
let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { return .result(dialog: "That message was empty.") }
guard await store.awaitLiveConnection() else { throw IntentError.macUnreachable }
store.sendInput(trimmed, to: SessionID(rawValue: session.id))
return .result(dialog: "Sent to \(session.title).")
}
static var parameterSummary: some ParameterSummary {
Summary("Tell \(\.$session) to \(\.$text)")
}
}
// MARK: - "What needs me?" (§4.3)
/// List the agents waiting on the user — for approval, or a next prompt — using the same attention
/// rule as the badge and NEEDS YOU section (`SessionStatus.needsYou`). Answers Siri's "what needs me
/// in Nucleic?" and feeds Shortcuts automations. Runs in the background; returns the list plus a
/// spoken summary.
struct SessionsNeedingMeIntent: AppIntent {
static let title: LocalizedStringResource = "Sessions Needing Me"
static let description = IntentDescription(
"List the Nucleic agents waiting on you — for approval or your next prompt.")
@MainActor
func perform() async throws -> some IntentResult & ReturnsValue<[SessionEntity]> & ProvidesDialog {
let store = RemoteStore.shared
guard store.isPaired else { throw IntentError.notPaired }
store.bootstrapForIntent()
// Give a cold process a brief moment to bring a channel up so the answer is current; fall
// back to the persisted list if the Mac stays unreachable.
_ = await store.awaitLiveConnection(timeout: 4)
let needy = store.liveSessions
.filter { $0.status.needsYou($0.disposition) }
.sorted(by: StatusStyle.attentionThenRecency)
.map(SessionEntity.init)
let dialog: IntentDialog
switch needy.count {
case 0: dialog = "Nothing needs you right now."
case 1: dialog = "One agent needs you: \(needy[0].title)."
default: dialog = "\(needy.count) agents need you."
}
return .result(value: needy, dialog: dialog)
}
}