Read App Intents Documentation
Nucleic-Session: 56C5F781-4444-466B-91CE-A7734A8E117F Co-authored-by: Nucleic <[email protected]>
This commit is contained in:
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user