@mmerterden/multi-agent-pipeline 20.36.0 → 20.37.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -0
- package/docs/facts.json +2 -2
- package/docs/features.md +1 -1
- package/docs/recovery-guide.md +1 -1
- package/install/catalog-history.json +1 -1
- package/manifest.json +225 -148
- package/package.json +1 -1
- package/pipeline/commands/multi-agent/SKILL.md +33 -154
- package/pipeline/commands/multi-agent/analysis/SKILL.md +14 -68
- package/pipeline/commands/multi-agent/analysis-jira/SKILL.md +5 -8
- package/pipeline/commands/multi-agent/autopilot-on/SKILL.md +38 -109
- package/pipeline/commands/multi-agent/channels/SKILL.md +38 -167
- package/pipeline/commands/multi-agent/complaint-analysis/SKILL.md +37 -144
- package/pipeline/commands/multi-agent/design-check/SKILL.md +27 -131
- package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +22 -48
- package/pipeline/commands/multi-agent/ios-coding-standard/SKILL.md +37 -180
- package/pipeline/commands/multi-agent/jira/SKILL.md +16 -46
- package/pipeline/commands/multi-agent/manual-test/SKILL.md +12 -7
- package/pipeline/commands/multi-agent/refactor/SKILL.md +23 -184
- package/pipeline/commands/multi-agent/resume/SKILL.md +30 -136
- package/pipeline/commands/multi-agent/review/SKILL.md +24 -199
- package/pipeline/commands/multi-agent/setup/SKILL.md +25 -181
- package/pipeline/commands/multi-agent/stack/SKILL.md +9 -53
- package/pipeline/commands/multi-agent/status/SKILL.md +13 -2
- package/pipeline/commands/multi-agent/store-ready/SKILL.md +27 -226
- package/pipeline/commands/multi-agent/sync/SKILL.md +35 -164
- package/pipeline/commands/multi-agent/test-gen/SKILL.md +2 -4
- package/pipeline/commands/multi-agent/update/SKILL.md +18 -60
- package/pipeline/contract/CHANGELOG.md +9 -0
- package/pipeline/contract/fixtures/error-invalid-request.json +1 -1
- package/pipeline/contract/fixtures/error-unauthorized.json +1 -1
- package/pipeline/contract/fixtures/error-unsigned.json +1 -1
- package/pipeline/contract/fixtures/runs-awaiting-question.json +1 -1
- package/pipeline/contract/fixtures/runs-empty.json +1 -1
- package/pipeline/contract/fixtures/runs-failed.json +1 -1
- package/pipeline/contract/fixtures/runs-pr-opened-redacted.json +1 -1
- package/pipeline/contract/fixtures/runs-pr-opened.json +1 -1
- package/pipeline/contract/fixtures/runs-running.json +1 -1
- package/pipeline/contract/manifest.json +5 -5
- package/pipeline/contract/types/index.d.ts +3 -2
- package/pipeline/multi-agent-refs/audit-guide.md +20 -0
- package/pipeline/multi-agent-refs/autopilot-on/details.md +148 -0
- package/pipeline/multi-agent-refs/channels/generator.md +14 -0
- package/pipeline/multi-agent-refs/channels/preview.md +13 -0
- package/pipeline/multi-agent-refs/channels/reference.md +46 -0
- package/pipeline/multi-agent-refs/complaint-analysis/phases.md +159 -0
- package/pipeline/multi-agent-refs/design-check/export.md +1 -0
- package/pipeline/multi-agent-refs/design-check/init-inventory.md +1 -1
- package/pipeline/multi-agent-refs/design-check/scope-and-ledger.md +64 -0
- package/pipeline/multi-agent-refs/features/analysis-command-notes.md +53 -0
- package/pipeline/multi-agent-refs/features/commit-multi-repo.md +63 -0
- package/pipeline/multi-agent-refs/features/dev-multi-repo.md +51 -0
- package/pipeline/multi-agent-refs/features/test-gap.md +40 -0
- package/pipeline/multi-agent-refs/features/worktree-finalize.md +1 -1
- package/pipeline/multi-agent-refs/garbage-collect/phases.md +60 -0
- package/pipeline/multi-agent-refs/ios-coding-standard/plan.md +71 -0
- package/pipeline/multi-agent-refs/ios-coding-standard/resolve-and-scan.md +135 -0
- package/pipeline/multi-agent-refs/jira/picker-detail.md +71 -0
- package/pipeline/multi-agent-refs/orchestrator/copilot-pipeline.md +88 -0
- package/pipeline/multi-agent-refs/orchestrator/dispatcher.md +96 -1
- package/pipeline/multi-agent-refs/orchestrator/phase-3-user-test.md +12 -9
- package/pipeline/multi-agent-refs/phases/phase-1-plan.md +4 -4
- package/pipeline/multi-agent-refs/phases/phase-2-dev.md +8 -47
- package/pipeline/multi-agent-refs/phases/phase-3-review.md +21 -65
- package/pipeline/multi-agent-refs/phases/phase-4-commit.md +9 -59
- package/pipeline/multi-agent-refs/phases/phase-5-report.md +0 -31
- package/pipeline/multi-agent-refs/refactor/sweep.md +190 -0
- package/pipeline/multi-agent-refs/resume/details.md +115 -2
- package/pipeline/multi-agent-refs/review/inputs.md +33 -1
- package/pipeline/multi-agent-refs/review/module-guides.md +9 -1
- package/pipeline/multi-agent-refs/review/panel.md +75 -0
- package/pipeline/multi-agent-refs/review/valven.md +5 -1
- package/pipeline/multi-agent-refs/rules.md +1 -1
- package/pipeline/multi-agent-refs/setup/hooks-and-plugins.md +4 -0
- package/pipeline/multi-agent-refs/setup/token-save-flow.md +13 -0
- package/pipeline/multi-agent-refs/stack/background.md +72 -0
- package/pipeline/multi-agent-refs/store-ready/gates.md +139 -0
- package/pipeline/multi-agent-refs/store-ready/report.md +40 -2
- package/pipeline/multi-agent-refs/sync/office.md +10 -0
- package/pipeline/multi-agent-refs/sync/overview.md +61 -0
- package/pipeline/multi-agent-refs/sync/repo.md +91 -0
- package/pipeline/multi-agent-refs/update/details.md +83 -0
- package/pipeline/office/index.html +6 -3
- package/pipeline/office/toolkit.json +1 -1
- package/pipeline/rules/figma-pipeline.md +6 -3
- package/pipeline/schemas/prefs.schema.json +1 -1
- package/pipeline/schemas/runs-index.schema.json +2 -1
- package/pipeline/scripts/_memory-policy.mjs +27 -5
- package/pipeline/scripts/checkpoint-fold.sh +59 -0
- package/pipeline/scripts/contract-server.mjs +1 -1
- package/pipeline/scripts/gate-ledger.mjs +1 -1
- package/pipeline/scripts/office-control.mjs +41 -15
- package/pipeline/scripts/offload-ref.sh +1 -1
- package/pipeline/scripts/rework-rulings.mjs +5 -3
- package/pipeline/scripts/runs-index.mjs +34 -7
- package/pipeline/scripts/scope-check-gate.mjs +15 -2
- package/pipeline/scripts/status-overview.mjs +401 -0
- package/pipeline/scripts/user-test-handoff.sh +203 -0
- package/pipeline/scripts/validate-planning.mjs +5 -3
- package/pipeline/scripts/validate-triage.mjs +2 -1
- package/pipeline/scripts/write-state.mjs +13 -4
- package/pipeline/skills/.skill-manifest.json +64 -64
- package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +7 -70
- package/pipeline/skills/shared/core/apple-archive-compliance/references/examples.md +67 -0
- package/pipeline/skills/shared/core/apple-archive-compliance/references/version-history.md +8 -0
- package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +10 -72
- package/pipeline/skills/shared/core/google-play-compliance/references/examples.md +69 -0
- package/pipeline/skills/shared/core/google-play-compliance/references/notes.md +7 -0
- package/pipeline/skills/shared/core/google-play-compliance/references/version-history.md +5 -0
- package/pipeline/skills/shared/core/multi-agent/SKILL.md +39 -262
- package/pipeline/skills/shared/core/multi-agent-autopilot-on/SKILL.md +35 -73
- package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +37 -337
- package/pipeline/skills/shared/core/multi-agent-design-check/SKILL.md +27 -122
- package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +8 -340
- package/pipeline/skills/shared/core/multi-agent-ios-coding-standard/SKILL.md +37 -180
- package/pipeline/skills/shared/core/multi-agent-manual-test/SKILL.md +12 -7
- package/pipeline/skills/shared/core/multi-agent-refactor/SKILL.md +23 -184
- package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +14 -82
- package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +25 -277
- package/pipeline/skills/shared/core/multi-agent-status/SKILL.md +13 -2
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +73 -217
- package/pipeline/skills/shared/external/alarmkit/SKILL.md +11 -162
- package/pipeline/skills/shared/external/alarmkit/references/alarmkit-code.md +257 -0
- package/pipeline/skills/shared/external/android-architecture/SKILL.md +7 -58
- package/pipeline/skills/shared/external/android-architecture/references/code-examples.md +68 -0
- package/pipeline/skills/shared/external/app-intents/SKILL.md +3 -45
- package/pipeline/skills/shared/external/app-intents/references/code-examples.md +54 -0
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +5 -81
- package/pipeline/skills/shared/external/app-store-optimization/references/engagement-and-localization.md +96 -0
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +6 -112
- package/pipeline/skills/shared/external/app-store-review/references/monetization-and-submission.md +138 -0
- package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +10 -117
- package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models-code.md +186 -0
- package/pipeline/skills/shared/external/authentication/SKILL.md +3 -50
- package/pipeline/skills/shared/external/authentication/references/code-examples.md +59 -0
- package/pipeline/skills/shared/external/callkit-voip/SKILL.md +3 -84
- package/pipeline/skills/shared/external/callkit-voip/references/code-examples.md +93 -0
- package/pipeline/skills/shared/external/ci-cd-pipelines/SKILL.md +7 -263
- package/pipeline/skills/shared/external/ci-cd-pipelines/references/pipeline-examples.md +281 -0
- package/pipeline/skills/shared/external/clean-code/SKILL.md +8 -41
- package/pipeline/skills/shared/external/clean-code/references/code-examples.md +56 -0
- package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +8 -146
- package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-code.md +214 -0
- package/pipeline/skills/shared/external/compose-components/SKILL.md +6 -80
- package/pipeline/skills/shared/external/compose-components/references/code-examples.md +89 -0
- package/pipeline/skills/shared/external/compose-navigation/SKILL.md +5 -64
- package/pipeline/skills/shared/external/compose-navigation/references/code-examples.md +70 -0
- package/pipeline/skills/shared/external/core-data/SKILL.md +9 -157
- package/pipeline/skills/shared/external/core-data/references/core-data-code.md +221 -0
- package/pipeline/skills/shared/external/core-nfc/SKILL.md +3 -89
- package/pipeline/skills/shared/external/core-nfc/references/code-examples.md +98 -0
- package/pipeline/skills/shared/external/coreml/SKILL.md +17 -133
- package/pipeline/skills/shared/external/coreml/references/coreml-code.md +208 -0
- package/pipeline/skills/shared/external/css-modern/SKILL.md +8 -131
- package/pipeline/skills/shared/external/css-modern/references/code-examples.md +142 -0
- package/pipeline/skills/shared/external/database-patterns/SKILL.md +6 -103
- package/pipeline/skills/shared/external/database-patterns/references/orm-examples.md +112 -0
- package/pipeline/skills/shared/external/html-semantic/SKILL.md +7 -48
- package/pipeline/skills/shared/external/html-semantic/references/code-examples.md +54 -0
- package/pipeline/skills/shared/external/humanizer/SKILL.md +6 -19
- package/pipeline/skills/shared/external/humanizer/references/voice.md +24 -0
- package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +13 -170
- package/pipeline/skills/shared/external/ios-accessibility/references/assistive-tech-review.md +224 -0
- package/pipeline/skills/shared/external/ios-localization/SKILL.md +2 -24
- package/pipeline/skills/shared/external/ios-localization/references/code-examples.md +30 -0
- package/pipeline/skills/shared/external/ios-networking/SKILL.md +3 -58
- package/pipeline/skills/shared/external/ios-networking/references/code-examples.md +67 -0
- package/pipeline/skills/shared/external/ios-security/SKILL.md +4 -64
- package/pipeline/skills/shared/external/ios-security/references/code-examples.md +74 -0
- package/pipeline/skills/shared/external/live-activities/SKILL.md +4 -90
- package/pipeline/skills/shared/external/live-activities/references/code-examples.md +108 -0
- package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +9 -23
- package/pipeline/skills/shared/external/localization-reuse-map/references/project-settings.md +22 -0
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +5 -95
- package/pipeline/skills/shared/external/mapkit-location/references/code-examples.md +117 -0
- package/pipeline/skills/shared/external/nextjs-app-router/SKILL.md +7 -106
- package/pipeline/skills/shared/external/nextjs-app-router/references/app-router-examples.md +124 -0
- package/pipeline/skills/shared/external/permissionkit/SKILL.md +2 -31
- package/pipeline/skills/shared/external/permissionkit/references/code-examples.md +37 -0
- package/pipeline/skills/shared/external/play-store-review/SKILL.md +6 -74
- package/pipeline/skills/shared/external/play-store-review/references/signing-and-policies.md +83 -0
- package/pipeline/skills/shared/external/push-notifications/SKILL.md +8 -125
- package/pipeline/skills/shared/external/push-notifications/references/notification-code.md +176 -0
- package/pipeline/skills/shared/external/python-patterns/SKILL.md +7 -158
- package/pipeline/skills/shared/external/python-patterns/references/python-examples.md +176 -0
- package/pipeline/skills/shared/external/rest-api-design/SKILL.md +7 -157
- package/pipeline/skills/shared/external/rest-api-design/references/api-examples.md +175 -0
- package/pipeline/skills/shared/external/swift-architecture/SKILL.md +11 -230
- package/pipeline/skills/shared/external/swift-architecture/references/pattern-examples.md +295 -0
- package/pipeline/skills/shared/external/swift-codable/SKILL.md +17 -149
- package/pipeline/skills/shared/external/swift-codable/references/codable-recipes.md +207 -0
- package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +12 -99
- package/pipeline/skills/shared/external/swift-concurrency/references/code-examples.md +100 -0
- package/pipeline/skills/shared/external/swift-concurrency/references/swift-6-2-additions.md +18 -0
- package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +9 -78
- package/pipeline/skills/shared/external/swift-formatstyle/references/formatstyle-examples.md +113 -0
- package/pipeline/skills/shared/external/swift-language/SKILL.md +3 -56
- package/pipeline/skills/shared/external/swift-language/references/code-examples.md +65 -0
- package/pipeline/skills/shared/external/swift-testing-pro/SKILL.md +1 -0
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +6 -122
- package/pipeline/skills/shared/external/swiftdata/references/code-examples.md +140 -0
- package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +12 -139
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/interop-code.md +198 -0
- package/pipeline/skills/shared/external/tailwind-css/SKILL.md +9 -81
- package/pipeline/skills/shared/external/tailwind-css/references/code-examples.md +91 -0
- package/pipeline/skills/shared/external/testing-backend/SKILL.md +7 -207
- package/pipeline/skills/shared/external/testing-backend/references/testing-examples.md +225 -0
- package/pipeline/skills/shared/external/typescript-patterns/SKILL.md +8 -89
- package/pipeline/skills/shared/external/typescript-patterns/references/code-examples.md +100 -0
- package/pipeline/skills/shared/external/vision-framework/SKILL.md +2 -20
- package/pipeline/skills/shared/external/vision-framework/references/code-examples.md +26 -0
- package/pipeline/skills/shared/external/vue-composition/SKILL.md +7 -169
- package/pipeline/skills/shared/external/vue-composition/references/vue-examples.md +187 -0
- package/pipeline/skills/shared/external/web-accessibility/SKILL.md +7 -174
- package/pipeline/skills/shared/external/web-accessibility/references/a11y-examples.md +192 -0
- package/pipeline/skills/shared/external/web-performance/SKILL.md +8 -66
- package/pipeline/skills/shared/external/web-performance/references/code-examples.md +77 -0
- package/pipeline/skills/shared/external/web-testing/SKILL.md +11 -186
- package/pipeline/skills/shared/external/web-testing/references/testing-examples.md +213 -0
- /package/pipeline/skills/shared/external/localization-reuse-map/{reference → references}/format-and-output.md +0 -0
- /package/pipeline/skills/shared/external/localization-reuse-map/{reference → references}/publish-and-snapshot.md +0 -0
- /package/pipeline/skills/shared/external/localization-reuse-map/{reference → references}/sources-and-recipes.md +0 -0
package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models-code.md
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Foundation Models and multi-backend code
|
|
2
|
+
|
|
3
|
+
<!-- toc -->
|
|
4
|
+
- [Check availability first](#check-availability-first)
|
|
5
|
+
- [Structured output](#structured-output)
|
|
6
|
+
- [Tools](#tools)
|
|
7
|
+
- [Errors](#errors)
|
|
8
|
+
- [Running several backends](#running-several-backends)
|
|
9
|
+
<!-- /toc -->
|
|
10
|
+
|
|
11
|
+
Code for availability checks, structured output, tools, error handling and routing between several backends. SKILL.md keeps stack choice, rules and review guidance.
|
|
12
|
+
|
|
13
|
+
## Check availability first
|
|
14
|
+
|
|
15
|
+
Every entry point checks availability and falls back without crashing.
|
|
16
|
+
|
|
17
|
+
```swift
|
|
18
|
+
import Foundation
|
|
19
|
+
import FoundationModels
|
|
20
|
+
import os
|
|
21
|
+
|
|
22
|
+
private let aiLog = Logger(subsystem: "StudyApp", category: "AI")
|
|
23
|
+
|
|
24
|
+
enum SummaryState { case ready, openSettings, downloading, unsupported }
|
|
25
|
+
|
|
26
|
+
func summaryState() -> SummaryState {
|
|
27
|
+
let model = SystemLanguageModel.default
|
|
28
|
+
switch model.availability {
|
|
29
|
+
case .available:
|
|
30
|
+
return model.supportsLocale(Locale.current) ? .ready : .unsupported
|
|
31
|
+
case .unavailable(.appleIntelligenceNotEnabled):
|
|
32
|
+
return .openSettings // point the user to Settings
|
|
33
|
+
case .unavailable(.modelNotReady):
|
|
34
|
+
return .downloading // assets still arriving; show progress
|
|
35
|
+
case .unavailable(.deviceNotEligible):
|
|
36
|
+
return .unsupported // hardware cannot run it; use a fallback
|
|
37
|
+
case .unavailable(let other):
|
|
38
|
+
aiLog.notice("Model unavailable: \(String(describing: other))")
|
|
39
|
+
return .unsupported
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Structured output
|
|
45
|
+
|
|
46
|
+
`@Generable` builds the output schema at compile time, and the response comes
|
|
47
|
+
back as your type.
|
|
48
|
+
|
|
49
|
+
```swift
|
|
50
|
+
@Generable
|
|
51
|
+
struct StudyCard {
|
|
52
|
+
@Guide(description: "The term being studied, two to four words")
|
|
53
|
+
var term: String
|
|
54
|
+
@Guide(description: "Plain-language definitions", .count(2))
|
|
55
|
+
var definitions: [String]
|
|
56
|
+
@Guide(.range(1...5))
|
|
57
|
+
var difficulty: Int
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let reply = try await tutor.respond(to: "Make a card for osmosis",
|
|
61
|
+
generating: StudyCard.self)
|
|
62
|
+
let card = reply.content
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`@Guide` options:
|
|
66
|
+
|
|
67
|
+
| Guide | Effect |
|
|
68
|
+
|---|---|
|
|
69
|
+
| `description:` | Natural-language hint for the field |
|
|
70
|
+
| `.anyOf([...])` | String must be one of the listed values |
|
|
71
|
+
| `.count(n)` | Array has exactly `n` elements |
|
|
72
|
+
| `.range(a...b)` | Number within a closed range |
|
|
73
|
+
| `.minimum(n)` / `.maximum(n)` | Lower or upper limit on a number |
|
|
74
|
+
| `.minimumCount(n)` / `.maximumCount(n)` | Limits on how many elements an array has |
|
|
75
|
+
| `.constant(value)` | Field is always exactly this |
|
|
76
|
+
| `.pattern(regex)` | String matches a pattern |
|
|
77
|
+
| `.element(guide)` | Applies a guide to every array element |
|
|
78
|
+
|
|
79
|
+
Fields are generated in declaration order. Put the fields that others depend on
|
|
80
|
+
first.
|
|
81
|
+
|
|
82
|
+
Streaming gives partial values as they form; each property of
|
|
83
|
+
`StudyCard.PartiallyGenerated` is optional:
|
|
84
|
+
|
|
85
|
+
```swift
|
|
86
|
+
let cardStream = tutor.streamResponse(to: "Card for diffusion", generating: StudyCard.self)
|
|
87
|
+
for try await partial in cardStream {
|
|
88
|
+
draft.term = partial.content.term ?? draft.term
|
|
89
|
+
draft.definitions = partial.content.definitions ?? draft.definitions
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Tools
|
|
94
|
+
|
|
95
|
+
```swift
|
|
96
|
+
struct GlossaryTool: Tool {
|
|
97
|
+
let name = "lookUpTerm"
|
|
98
|
+
let description = "Returns the course glossary entry for a biology term."
|
|
99
|
+
|
|
100
|
+
@Generable
|
|
101
|
+
struct Arguments {
|
|
102
|
+
@Guide(description: "Singular form of the term to look up")
|
|
103
|
+
var term: String
|
|
104
|
+
@Guide(.range(1...3))
|
|
105
|
+
var gradeLevel: Int
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
func call(arguments: Arguments) async throws -> String {
|
|
109
|
+
await Glossary.shared.entry(for: arguments.term, level: arguments.gradeLevel)
|
|
110
|
+
?? "No entry found."
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
- Register tools when creating the session, and only the ones the task needs.
|
|
116
|
+
- `Tool` is `Sendable`; captured state must be safe to share.
|
|
117
|
+
- Tool names, descriptions and argument schemas, like `@Generable` schemas,
|
|
118
|
+
use up part of the context budget.
|
|
119
|
+
- Whether a tool runs is the model's decision. Data the answer always needs
|
|
120
|
+
should be fetched beforehand and put in the prompt; keep tools for lookups
|
|
121
|
+
that depend on the conversation.
|
|
122
|
+
|
|
123
|
+
## Errors
|
|
124
|
+
|
|
125
|
+
```swift
|
|
126
|
+
do {
|
|
127
|
+
let answer = try await tutor.respond(to: question)
|
|
128
|
+
show(answer.content)
|
|
129
|
+
} catch let failure as LanguageModelSession.GenerationError {
|
|
130
|
+
switch failure {
|
|
131
|
+
case .exceededContextWindowSize: restartWithSummary() // too many tokens
|
|
132
|
+
case .guardrailViolation: showRephraseHint() // safety filter hit
|
|
133
|
+
case .concurrentRequests: queueUntilIdle() // session already busy
|
|
134
|
+
case .unsupportedLanguageOrLocale: showLanguageNotice()
|
|
135
|
+
case .unsupportedGuide: logSchemaProblem() // a @Guide the model cannot honour
|
|
136
|
+
case .assetsUnavailable: showDownloadingState()
|
|
137
|
+
case .refusal(let refusal, _): explain(refusal) // stream refusal.explanation
|
|
138
|
+
case .rateLimited: retryLater() // back off
|
|
139
|
+
case .decodingFailure: logSchemaProblem() // output did not fit the type
|
|
140
|
+
default: showGenericFailure()
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Running several backends
|
|
146
|
+
|
|
147
|
+
Try the system model first, then a local open model, and fail clearly when
|
|
148
|
+
neither is there:
|
|
149
|
+
|
|
150
|
+
```swift
|
|
151
|
+
func pickBackend() async throws -> Backend {
|
|
152
|
+
let systemModel = SystemLanguageModel.default
|
|
153
|
+
if systemModel.isAvailable { return .foundationModels }
|
|
154
|
+
if await mlxModelCanLoad() { return .mlx }
|
|
155
|
+
throw AIError.noBackendAvailable
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Send every model call through one actor so requests never compete for the same
|
|
160
|
+
hardware. Actors are reentrant: a method that awaits lets the next caller in,
|
|
161
|
+
so an actor that only forwards the call does not serialise anything. Chain the
|
|
162
|
+
work instead:
|
|
163
|
+
|
|
164
|
+
```swift
|
|
165
|
+
actor InferenceGate {
|
|
166
|
+
private var last: Task<Void, Never>?
|
|
167
|
+
|
|
168
|
+
func exclusive<Value: Sendable>(
|
|
169
|
+
_ work: @escaping @Sendable () async throws -> Value
|
|
170
|
+
) async throws -> Value {
|
|
171
|
+
let previous = last
|
|
172
|
+
let current = Task { () async throws -> Value in
|
|
173
|
+
await previous?.value
|
|
174
|
+
return try await work()
|
|
175
|
+
}
|
|
176
|
+
last = Task { _ = try? await current.value }
|
|
177
|
+
return try await current.value
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
- For a custom Core ML model, this skill stops at conversion and
|
|
183
|
+
optimisation. Swift integration, loading, Vision hookup and running
|
|
184
|
+
predictions are for `coreml`.
|
|
185
|
+
- Private material such as journals or health notes stays on the device unless
|
|
186
|
+
the product has deliberately chosen a non-local fallback.
|
|
@@ -51,32 +51,7 @@ extension WelcomeViewController: ASAuthorizationControllerPresentationContextPro
|
|
|
51
51
|
|
|
52
52
|
### Delegate callbacks
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
extension WelcomeViewController: ASAuthorizationControllerDelegate {
|
|
56
|
-
func authorizationController(controller: ASAuthorizationController,
|
|
57
|
-
didCompleteWithAuthorization authorization: ASAuthorization) {
|
|
58
|
-
let credential = authorization.credential
|
|
59
|
-
guard let apple = credential as? ASAuthorizationAppleIDCredential else { return }
|
|
60
|
-
AccountVault.shared.rememberAppleUser(apple.user) // stable per-team ID
|
|
61
|
-
if let name = apple.fullName { ProfileDraft.shared.name = name } // first time only
|
|
62
|
-
if let mail = apple.email { ProfileDraft.shared.email = mail } // first time only
|
|
63
|
-
Task { try await SessionAPI.exchange(token: apple.identityToken, code: apple.authorizationCode) }
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
func authorizationController(controller: ASAuthorizationController,
|
|
67
|
-
didCompleteWithError error: Error) {
|
|
68
|
-
guard let authError = error as? ASAuthorizationError else { return }
|
|
69
|
-
switch authError.code {
|
|
70
|
-
case .canceled: break // user closed the sheet
|
|
71
|
-
case .notInteractive: break // silent check found nothing
|
|
72
|
-
case .failed, .invalidResponse, .notHandled:
|
|
73
|
-
showSignInProblem(authError)
|
|
74
|
-
default:
|
|
75
|
-
showSignInProblem(authError)
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
```
|
|
54
|
+
Code: [Delegate callbacks](references/code-examples.md#delegate-callbacks).
|
|
80
55
|
|
|
81
56
|
`user` is a stable identifier scoped to your team; save it in the Keychain so
|
|
82
57
|
you can check credential state later. `identityToken` is a JWT for the server
|
|
@@ -138,30 +113,7 @@ user out as soon as it fires.
|
|
|
138
113
|
Never accept the identity token on the device alone. Hand both values to your
|
|
139
114
|
backend and keep only what it returns:
|
|
140
115
|
|
|
141
|
-
|
|
142
|
-
enum SessionAPI {
|
|
143
|
-
struct Session: Decodable { let accessToken: String }
|
|
144
|
-
private static let transport = URLSession(configuration: .ephemeral)
|
|
145
|
-
|
|
146
|
-
static func exchange(token: Data?, code: Data?) async throws {
|
|
147
|
-
guard let token, let code,
|
|
148
|
-
let jwt = String(data: token, encoding: .utf8),
|
|
149
|
-
let grant = String(data: code, encoding: .utf8) else { throw URLError(.badServerResponse) }
|
|
150
|
-
|
|
151
|
-
guard let endpoint = URL(string: "https://api.tasknest.app/auth/apple") else { throw URLError(.badURL) }
|
|
152
|
-
var call = URLRequest(url: endpoint)
|
|
153
|
-
call.httpMethod = "POST"
|
|
154
|
-
call.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
155
|
-
let body = try JSONEncoder().encode(["identity_token": jwt, "authorization_code": grant])
|
|
156
|
-
let (bytes, reply) = try await transport.upload(for: call, from: body)
|
|
157
|
-
let status = (reply as? HTTPURLResponse)?.statusCode ?? 0
|
|
158
|
-
if status != 200 { throw URLError(.userAuthenticationRequired) }
|
|
159
|
-
|
|
160
|
-
let session = try JSONDecoder().decode(Session.self, from: bytes)
|
|
161
|
-
try TokenVault(service: "auth").store(Data(session.accessToken.utf8), account: "access")
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
```
|
|
116
|
+
Code: [Server validation](references/code-examples.md#server-validation).
|
|
165
117
|
|
|
166
118
|
`TokenVault` is in the [Keychain reference](references/keychain-biometric.md). The
|
|
167
119
|
server verifies the JWT signature against Apple's published key set (the JWKS
|
|
@@ -388,3 +340,4 @@ mapping against OWASP MASVS/MASTG. App and device attestation belongs to `device
|
|
|
388
340
|
- [ASAuthorizationPasswordProvider](https://developer.apple.com/documentation/authenticationservices/asauthorizationpasswordprovider)
|
|
389
341
|
- [SignInWithAppleButton](https://developer.apple.com/documentation/authenticationservices/signinwithapplebutton)
|
|
390
342
|
- Sample code: the AuthenticationServices sample on implementing user authentication with Apple ID sign-in
|
|
343
|
+
- [Code examples](references/code-examples.md): open for the full code behind Delegate callbacks, Server validation.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Authentication Code Examples
|
|
2
|
+
|
|
3
|
+
Full code samples for the SKILL.md sections that link here. The rules and the decision guidance stay in SKILL.md; each heading below matches the SKILL.md section the code belongs to.
|
|
4
|
+
|
|
5
|
+
## Delegate callbacks
|
|
6
|
+
|
|
7
|
+
```swift
|
|
8
|
+
extension WelcomeViewController: ASAuthorizationControllerDelegate {
|
|
9
|
+
func authorizationController(controller: ASAuthorizationController,
|
|
10
|
+
didCompleteWithAuthorization authorization: ASAuthorization) {
|
|
11
|
+
let credential = authorization.credential
|
|
12
|
+
guard let apple = credential as? ASAuthorizationAppleIDCredential else { return }
|
|
13
|
+
AccountVault.shared.rememberAppleUser(apple.user) // stable per-team ID
|
|
14
|
+
if let name = apple.fullName { ProfileDraft.shared.name = name } // first time only
|
|
15
|
+
if let mail = apple.email { ProfileDraft.shared.email = mail } // first time only
|
|
16
|
+
Task { try await SessionAPI.exchange(token: apple.identityToken, code: apple.authorizationCode) }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
func authorizationController(controller: ASAuthorizationController,
|
|
20
|
+
didCompleteWithError error: Error) {
|
|
21
|
+
guard let authError = error as? ASAuthorizationError else { return }
|
|
22
|
+
switch authError.code {
|
|
23
|
+
case .canceled: break // user closed the sheet
|
|
24
|
+
case .notInteractive: break // silent check found nothing
|
|
25
|
+
case .failed, .invalidResponse, .notHandled:
|
|
26
|
+
showSignInProblem(authError)
|
|
27
|
+
default:
|
|
28
|
+
showSignInProblem(authError)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Server validation
|
|
35
|
+
|
|
36
|
+
```swift
|
|
37
|
+
enum SessionAPI {
|
|
38
|
+
struct Session: Decodable { let accessToken: String }
|
|
39
|
+
private static let transport = URLSession(configuration: .ephemeral)
|
|
40
|
+
|
|
41
|
+
static func exchange(token: Data?, code: Data?) async throws {
|
|
42
|
+
guard let token, let code,
|
|
43
|
+
let jwt = String(data: token, encoding: .utf8),
|
|
44
|
+
let grant = String(data: code, encoding: .utf8) else { throw URLError(.badServerResponse) }
|
|
45
|
+
|
|
46
|
+
guard let endpoint = URL(string: "https://api.tasknest.app/auth/apple") else { throw URLError(.badURL) }
|
|
47
|
+
var call = URLRequest(url: endpoint)
|
|
48
|
+
call.httpMethod = "POST"
|
|
49
|
+
call.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
50
|
+
let body = try JSONEncoder().encode(["identity_token": jwt, "authorization_code": grant])
|
|
51
|
+
let (bytes, reply) = try await transport.upload(for: call, from: body)
|
|
52
|
+
let status = (reply as? HTTPURLResponse)?.statusCode ?? 0
|
|
53
|
+
if status != 200 { throw URLError(.userAuthenticationRequired) }
|
|
54
|
+
|
|
55
|
+
let session = try JSONDecoder().decode(Session.self, from: bytes)
|
|
56
|
+
try TokenVault(service: "auth").store(Data(session.accessToken.utf8), account: "access")
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
@@ -145,34 +145,7 @@ ended with `.failed` and fail the action.
|
|
|
145
145
|
Outgoing calls start with a request to `CXCallController`. The system checks it
|
|
146
146
|
and hands it back through `provider(_:perform:)` with a `CXStartCallAction`.
|
|
147
147
|
|
|
148
|
-
|
|
149
|
-
extension PhoneLine {
|
|
150
|
-
func dial(_ address: String, video: Bool) -> UUID {
|
|
151
|
-
let id = UUID()
|
|
152
|
-
let start = CXStartCallAction(call: id, handle: CXHandle(type: .phoneNumber, value: address))
|
|
153
|
-
start.isVideo = video
|
|
154
|
-
controller.request(CXTransaction(action: start)) { error in
|
|
155
|
-
if let error { Log.calls.error("dial refused: \(error)") }
|
|
156
|
-
}
|
|
157
|
-
return id
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
func provider(_ cx: CXProvider, perform dial: CXStartCallAction) {
|
|
161
|
-
AudioRoute.prepareForCall()
|
|
162
|
-
let id = dial.callUUID
|
|
163
|
-
cx.reportOutgoingCall(with: id, startedConnectingAt: .now)
|
|
164
|
-
Task {
|
|
165
|
-
do {
|
|
166
|
-
try await SignalingClient.shared.place(call: id, to: dial.handle.value)
|
|
167
|
-
cx.reportOutgoingCall(with: id, connectedAt: .now)
|
|
168
|
-
dial.fulfill()
|
|
169
|
-
} catch {
|
|
170
|
-
dial.fail()
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
```
|
|
148
|
+
Code: [Outgoing call flow](references/code-examples.md#outgoing-call-flow).
|
|
176
149
|
|
|
177
150
|
Report both `startedConnectingAt` and `connectedAt`; Recents and the call timer
|
|
178
151
|
use them.
|
|
@@ -199,62 +172,7 @@ Report rules:
|
|
|
199
172
|
| 26.4+ delegate, `mustReport == false` | No | After local handling |
|
|
200
173
|
| Older delegate | Treat every VoIP call push as required | After the report callback |
|
|
201
174
|
|
|
202
|
-
|
|
203
|
-
import PushKit
|
|
204
|
-
|
|
205
|
-
@MainActor
|
|
206
|
-
final class VoIPInbox: NSObject, @preconcurrency PKPushRegistryDelegate {
|
|
207
|
-
private let registry = PKPushRegistry(queue: .main)
|
|
208
|
-
|
|
209
|
-
func start() {
|
|
210
|
-
registry.delegate = self
|
|
211
|
-
registry.desiredPushTypes = [.voIP]
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
func pushRegistry(_ reg: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) {
|
|
215
|
-
let hex = credentials.token.reduce(into: "") { out, byte in out += String(format: "%02x", byte) }
|
|
216
|
-
Backend.shared.upload(voipToken: hex)
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
@available(iOS 26.4, *)
|
|
220
|
-
func pushRegistry(_ reg: PKPushRegistry,
|
|
221
|
-
didReceiveIncomingVoIPPushWith payload: PKPushPayload,
|
|
222
|
-
metadata: PKVoIPPushMetadata,
|
|
223
|
-
withCompletionHandler completion: @escaping @Sendable () -> Void) {
|
|
224
|
-
guard metadata.mustReport else {
|
|
225
|
-
completion()
|
|
226
|
-
return
|
|
227
|
-
}
|
|
228
|
-
ring(from: payload, then: completion)
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
func pushRegistry(_ reg: PKPushRegistry,
|
|
232
|
-
didReceiveIncomingPushWith payload: PKPushPayload,
|
|
233
|
-
for type: PKPushType,
|
|
234
|
-
completion: @escaping () -> Void) {
|
|
235
|
-
guard type == .voIP else {
|
|
236
|
-
completion()
|
|
237
|
-
return
|
|
238
|
-
}
|
|
239
|
-
ring(from: payload, then: completion)
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
private func ring(from payload: PKPushPayload, then completion: @escaping () -> Void) {
|
|
243
|
-
let body = payload.dictionaryPayload
|
|
244
|
-
let number = body["from"] as? String ?? "Unknown"
|
|
245
|
-
let name = body["display"] as? String ?? number
|
|
246
|
-
let video = body["video"] as? Bool ?? false
|
|
247
|
-
Task {
|
|
248
|
-
do {
|
|
249
|
-
try await PhoneLine.shared.announceIncoming(id: UUID(), number: number, name: name, video: video)
|
|
250
|
-
} catch {
|
|
251
|
-
Log.calls.notice("call filtered or rejected: \(error)")
|
|
252
|
-
}
|
|
253
|
-
completion()
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
```
|
|
175
|
+
Code: [PushKit VoIP registration](references/code-examples.md#pushkit-voip-registration).
|
|
258
176
|
|
|
259
177
|
Keep the older delegate method for iOS 26.0 through 26.3 and for lower
|
|
260
178
|
deployment targets. The registry delivers on `.main`, which is why the inbox is
|
|
@@ -420,3 +338,4 @@ Rotating APNs keys and the usual notification registration are covered by
|
|
|
420
338
|
- [PKVoIPPushMetadata](https://developer.apple.com/documentation/pushkit/pkvoippushmetadata)
|
|
421
339
|
- Article: [VoIP call handling with CallKit](https://developer.apple.com/documentation/callkit/making-and-receiving-voip-calls)
|
|
422
340
|
- Article: [PushKit VoIP notification handling](https://developer.apple.com/documentation/pushkit/responding-to-voip-notifications-from-pushkit)
|
|
341
|
+
- [Code examples](references/code-examples.md): open for the full code behind Outgoing call flow, PushKit VoIP registration.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# CallKit and PushKit Code Examples
|
|
2
|
+
|
|
3
|
+
Full code samples for the SKILL.md sections that link here. The rules and the decision guidance stay in SKILL.md; each heading below matches the SKILL.md section the code belongs to.
|
|
4
|
+
|
|
5
|
+
## Outgoing call flow
|
|
6
|
+
|
|
7
|
+
```swift
|
|
8
|
+
extension PhoneLine {
|
|
9
|
+
func dial(_ address: String, video: Bool) -> UUID {
|
|
10
|
+
let id = UUID()
|
|
11
|
+
let start = CXStartCallAction(call: id, handle: CXHandle(type: .phoneNumber, value: address))
|
|
12
|
+
start.isVideo = video
|
|
13
|
+
controller.request(CXTransaction(action: start)) { error in
|
|
14
|
+
if let error { Log.calls.error("dial refused: \(error)") }
|
|
15
|
+
}
|
|
16
|
+
return id
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
func provider(_ cx: CXProvider, perform dial: CXStartCallAction) {
|
|
20
|
+
AudioRoute.prepareForCall()
|
|
21
|
+
let id = dial.callUUID
|
|
22
|
+
cx.reportOutgoingCall(with: id, startedConnectingAt: .now)
|
|
23
|
+
Task {
|
|
24
|
+
do {
|
|
25
|
+
try await SignalingClient.shared.place(call: id, to: dial.handle.value)
|
|
26
|
+
cx.reportOutgoingCall(with: id, connectedAt: .now)
|
|
27
|
+
dial.fulfill()
|
|
28
|
+
} catch {
|
|
29
|
+
dial.fail()
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## PushKit VoIP registration
|
|
37
|
+
|
|
38
|
+
```swift
|
|
39
|
+
import PushKit
|
|
40
|
+
|
|
41
|
+
@MainActor
|
|
42
|
+
final class VoIPInbox: NSObject, @preconcurrency PKPushRegistryDelegate {
|
|
43
|
+
private let registry = PKPushRegistry(queue: .main)
|
|
44
|
+
|
|
45
|
+
func start() {
|
|
46
|
+
registry.delegate = self
|
|
47
|
+
registry.desiredPushTypes = [.voIP]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
func pushRegistry(_ reg: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) {
|
|
51
|
+
let hex = credentials.token.reduce(into: "") { out, byte in out += String(format: "%02x", byte) }
|
|
52
|
+
Backend.shared.upload(voipToken: hex)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@available(iOS 26.4, *)
|
|
56
|
+
func pushRegistry(_ reg: PKPushRegistry,
|
|
57
|
+
didReceiveIncomingVoIPPushWith payload: PKPushPayload,
|
|
58
|
+
metadata: PKVoIPPushMetadata,
|
|
59
|
+
withCompletionHandler completion: @escaping @Sendable () -> Void) {
|
|
60
|
+
guard metadata.mustReport else {
|
|
61
|
+
completion()
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
ring(from: payload, then: completion)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
func pushRegistry(_ reg: PKPushRegistry,
|
|
68
|
+
didReceiveIncomingPushWith payload: PKPushPayload,
|
|
69
|
+
for type: PKPushType,
|
|
70
|
+
completion: @escaping () -> Void) {
|
|
71
|
+
guard type == .voIP else {
|
|
72
|
+
completion()
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
ring(from: payload, then: completion)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private func ring(from payload: PKPushPayload, then completion: @escaping () -> Void) {
|
|
79
|
+
let body = payload.dictionaryPayload
|
|
80
|
+
let number = body["from"] as? String ?? "Unknown"
|
|
81
|
+
let name = body["display"] as? String ?? number
|
|
82
|
+
let video = body["video"] as? Bool ?? false
|
|
83
|
+
Task {
|
|
84
|
+
do {
|
|
85
|
+
try await PhoneLine.shared.announceIncoming(id: UUID(), number: number, name: name, video: video)
|
|
86
|
+
} catch {
|
|
87
|
+
Log.calls.notice("call filtered or rejected: \(error)")
|
|
88
|
+
}
|
|
89
|
+
completion()
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|