@agimon-ai/po-native 0.0.0-stage → 0.0.1-alpha.4

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.
Files changed (182) hide show
  1. package/LICENSE +149 -0
  2. package/README.md +161 -2
  3. package/app.config.ts +84 -0
  4. package/assets/icon.png +0 -0
  5. package/assets/welcome-artwork.png +0 -0
  6. package/assets/welcome-logo-dark.png +0 -0
  7. package/assets/welcome-logo-light.png +0 -0
  8. package/babel.config.cjs +8 -0
  9. package/generated/richAssets.json +1 -0
  10. package/metro.config.cjs +48 -0
  11. package/modules/po-device-key/android/build.gradle +19 -0
  12. package/modules/po-device-key/android/src/main/java/expo/modules/podevicekey/PoDeviceKeyModule.kt +174 -0
  13. package/modules/po-device-key/expo-module.config.json +5 -0
  14. package/modules/po-device-key/index.ts +72 -0
  15. package/modules/po-device-key/ios/PoDeviceKey.podspec +17 -0
  16. package/modules/po-device-key/ios/PoDeviceKeyModule.swift +134 -0
  17. package/modules/po-webrtc-speaker/expo-module.config.json +6 -0
  18. package/modules/po-webrtc-speaker/ios/PoWebRtcSpeaker.podspec +24 -0
  19. package/modules/po-webrtc-speaker/ios/PoWebRtcSpeakerAppDelegate.swift +20 -0
  20. package/package.json +129 -3
  21. package/src/adapters/abortSignal.ts +42 -0
  22. package/src/adapters/crypto.ts +31 -0
  23. package/src/adapters/remoteHttp.ts +153 -0
  24. package/src/adapters/remoteSocket.ts +176 -0
  25. package/src/app/AccountRoute.tsx +706 -0
  26. package/src/app/App.tsx +3121 -0
  27. package/src/app/GrantNotices.tsx +37 -0
  28. package/src/app/HostsRoutes.tsx +506 -0
  29. package/src/app/appearance.ts +40 -0
  30. package/src/app/cloudRuntime.ts +390 -0
  31. package/src/app/firstSteps.ts +28 -0
  32. package/src/app/homeState.ts +99 -0
  33. package/src/app/hostRuntimes.ts +398 -0
  34. package/src/app/liveRuntime.ts +691 -0
  35. package/src/app/nativeTemplate.tsx +142 -0
  36. package/src/app/otherWorkspaces.ts +27 -0
  37. package/src/app/railWorkspaces.ts +67 -0
  38. package/src/app/sessionView.ts +27 -0
  39. package/src/app/useHome.ts +233 -0
  40. package/src/components/ActivityPanel.tsx +49 -0
  41. package/src/components/ActivitySheet.stories.tsx +64 -0
  42. package/src/components/ActivitySheet.tsx +311 -0
  43. package/src/components/CaptureTray.tsx +93 -0
  44. package/src/components/CompletionSheet.stories.tsx +126 -0
  45. package/src/components/CompletionSheet.tsx +378 -0
  46. package/src/components/Composer.tsx +453 -0
  47. package/src/components/ConnectCard.tsx +171 -0
  48. package/src/components/ContextFace.stories.tsx +94 -0
  49. package/src/components/ContextFace.tsx +426 -0
  50. package/src/components/DashboardTiles.tsx +148 -0
  51. package/src/components/DialogSheet.stories.tsx +100 -0
  52. package/src/components/DialogSheet.tsx +114 -0
  53. package/src/components/GrantNotice.tsx +54 -0
  54. package/src/components/MessageActionsSheet.stories.tsx +45 -0
  55. package/src/components/MessageActionsSheet.tsx +37 -0
  56. package/src/components/RestoreCard.tsx +73 -0
  57. package/src/components/ResumeSheetView.tsx +125 -0
  58. package/src/components/SessionChoicesView.tsx +218 -0
  59. package/src/components/SessionOptionsSheet.tsx +429 -0
  60. package/src/components/SessionSheets.tsx +152 -0
  61. package/src/components/SocialSignInButton.stories.tsx +25 -0
  62. package/src/components/SocialSignInButton.tsx +30 -0
  63. package/src/components/TabBar.stories.tsx +48 -0
  64. package/src/components/TabBar.tsx +117 -0
  65. package/src/components/TimelineItem.tsx +238 -0
  66. package/src/components/contextRows.ts +48 -0
  67. package/src/components/homeApps.ts +69 -0
  68. package/src/components/richActivity/AuthorMediaHost.tsx +405 -0
  69. package/src/components/richActivity/ImageViewer.stories.tsx +49 -0
  70. package/src/components/richActivity/ImageViewer.tsx +92 -0
  71. package/src/components/richActivity/McpAppChrome.stories.tsx +62 -0
  72. package/src/components/richActivity/McpAppChrome.tsx +54 -0
  73. package/src/components/richActivity/McpAppView.tsx +289 -0
  74. package/src/components/richActivity/NativeAuthorCaptureSurface.tsx +180 -0
  75. package/src/components/richActivity/NativeAuthorHtmlView.tsx +100 -0
  76. package/src/components/richActivity/RichActivityPreview.stories.tsx +115 -0
  77. package/src/components/richActivity/authorHtmlPolicy.ts +70 -0
  78. package/src/components/richActivity/authorMediaFiles.ts +133 -0
  79. package/src/components/richActivity/authorMediaValidation.ts +111 -0
  80. package/src/components/richActivity/createRichActivityRenderers.tsx +486 -0
  81. package/src/components/richActivity/isolatedContent.ts +36 -0
  82. package/src/components/richActivity/mcpAppBridge.ts +165 -0
  83. package/src/components/richActivity/offlineDocuments.ts +54 -0
  84. package/src/components/richActivity/terminalFollow.ts +110 -0
  85. package/src/components/slotProps.ts +159 -0
  86. package/src/components/tabHeader.ts +5 -0
  87. package/src/index.tsx +50 -0
  88. package/src/screens/AccountOnboarding.stories.tsx +121 -0
  89. package/src/screens/AccountScreen.tsx +700 -0
  90. package/src/screens/ActionFlowDesign.stories.tsx +476 -0
  91. package/src/screens/ActivityDesign.stories.tsx +361 -0
  92. package/src/screens/AppearanceScreen.tsx +240 -0
  93. package/src/screens/ClientDevices.stories.tsx +56 -0
  94. package/src/screens/ClientDevices.tsx +119 -0
  95. package/src/screens/ClientDevicesView.tsx +127 -0
  96. package/src/screens/ComposerOptionsDesign.stories.tsx +363 -0
  97. package/src/screens/ComputerDetail.stories.tsx +48 -0
  98. package/src/screens/ContributedSettingsScreen.tsx +418 -0
  99. package/src/screens/ConversationDesign.stories.tsx +446 -0
  100. package/src/screens/GitRemoteScreen.tsx +207 -0
  101. package/src/screens/Grants.stories.tsx +47 -0
  102. package/src/screens/HomeComputers.stories.tsx +84 -0
  103. package/src/screens/HomeConnectDesign.stories.tsx +233 -0
  104. package/src/screens/HomeScreen.stories.tsx +542 -0
  105. package/src/screens/HomeScreen.tsx +678 -0
  106. package/src/screens/HostDrawer.stories.tsx +35 -0
  107. package/src/screens/HostDrawer.tsx +284 -0
  108. package/src/screens/HostsView.tsx +391 -0
  109. package/src/screens/ImagesScreen.tsx +115 -0
  110. package/src/screens/LargeContentDesign.stories.tsx +481 -0
  111. package/src/screens/McpAddServerDesign.stories.tsx +72 -0
  112. package/src/screens/McpServersScreen.tsx +428 -0
  113. package/src/screens/MetricsScreen.tsx +450 -0
  114. package/src/screens/MultiHostDesign.stories.tsx +260 -0
  115. package/src/screens/NativeListsDesign.stories.tsx +734 -0
  116. package/src/screens/PairingScreen.stories.tsx +34 -0
  117. package/src/screens/PairingScreen.tsx +113 -0
  118. package/src/screens/PluginActionsDesign.stories.tsx +107 -0
  119. package/src/screens/PluginsScreen.tsx +97 -0
  120. package/src/screens/ProvidersScreen.stories.tsx +40 -0
  121. package/src/screens/ProvidersScreen.tsx +643 -0
  122. package/src/screens/RemoteControlScreen.tsx +226 -0
  123. package/src/screens/SessionActionsDesign.stories.tsx +475 -0
  124. package/src/screens/SessionListScreen.stories.tsx +32 -0
  125. package/src/screens/SessionListScreen.tsx +137 -0
  126. package/src/screens/SessionScreen.stories.tsx +231 -0
  127. package/src/screens/SessionScreen.tsx +666 -0
  128. package/src/screens/SettingsComputers.stories.tsx +57 -0
  129. package/src/screens/SettingsDesign.stories.tsx +219 -0
  130. package/src/screens/SettingsFields.tsx +332 -0
  131. package/src/screens/SettingsParityDesign.stories.tsx +508 -0
  132. package/src/screens/SettingsRound2Design.stories.tsx +1092 -0
  133. package/src/screens/SettingsScreen.stories.tsx +321 -0
  134. package/src/screens/SettingsScreen.tsx +424 -0
  135. package/src/screens/WorkspaceProviders.stories.tsx +109 -0
  136. package/src/screens/WorkspaceProviders.tsx +201 -0
  137. package/src/screens/WorkspaceProvidersSection.tsx +226 -0
  138. package/src/screens/WorkspaceScreen.stories.tsx +163 -0
  139. package/src/screens/WorkspaceScreen.tsx +46 -0
  140. package/src/screens/WorkspaceSettingsScreen.tsx +614 -0
  141. package/src/screens/accountBack.ts +15 -0
  142. package/src/screens/designCaptures.ts +26 -0
  143. package/src/screens/designFrame.tsx +32 -0
  144. package/src/screens/homeModel.ts +309 -0
  145. package/src/screens/workspaceModel.ts +23 -0
  146. package/src/services/accountClient/index.ts +292 -0
  147. package/src/services/accountGrants/index.ts +147 -0
  148. package/src/services/accountRoster/index.ts +390 -0
  149. package/src/services/accountTrust/index.ts +261 -0
  150. package/src/services/apiTransport/index.ts +202 -0
  151. package/src/services/bundleCache/index.ts +240 -0
  152. package/src/services/byteCodec/index.ts +10 -0
  153. package/src/services/calendarTargets/index.ts +37 -0
  154. package/src/services/clientIdentity/index.ts +77 -0
  155. package/src/services/cloudHosts/index.ts +166 -0
  156. package/src/services/compositionLoader/index.ts +21 -0
  157. package/src/services/connection/index.ts +107 -0
  158. package/src/services/deviceTrust/index.ts +767 -0
  159. package/src/services/hostActions/index.ts +381 -0
  160. package/src/services/hostLinkBroker/index.ts +208 -0
  161. package/src/services/hostModules/index.ts +87 -0
  162. package/src/services/hostSnapshots/index.ts +210 -0
  163. package/src/services/issuerSelection.native.ts +27 -0
  164. package/src/services/legacyAppBundle/index.ts +11 -0
  165. package/src/services/logExport/index.ts +185 -0
  166. package/src/services/nativeAudio/index.ts +295 -0
  167. package/src/services/nativeAuthorDrafts/index.ts +191 -0
  168. package/src/services/nativePush/index.ts +201 -0
  169. package/src/services/nativeRealtime/index.ts +120 -0
  170. package/src/services/nativeVoice/index.ts +82 -0
  171. package/src/services/pairing/index.ts +235 -0
  172. package/src/services/pluginRuntime/index.ts +353 -0
  173. package/src/services/runtimeProof/index.ts +75 -0
  174. package/src/services/sessionProfiles/index.ts +98 -0
  175. package/src/services/sessionProfiles/type.ts +14 -0
  176. package/src/services/sessionRuntime/index.ts +214 -0
  177. package/src/services/settingsApi/index.ts +560 -0
  178. package/src/services/workspaceProviders/index.ts +153 -0
  179. package/src/types/assets.d.ts +4 -0
  180. package/src/types/poTemplateAdvanced.d.ts +9 -0
  181. package/src/unistyles.ts +18 -0
  182. package/styles/preview.css +10 -0
package/LICENSE ADDED
@@ -0,0 +1,149 @@
1
+ License text copyright © 2017 MariaDB Corporation Ab, All Rights Reserved.
2
+ "Business Source License" is a trademark of MariaDB Corporation Ab.
3
+
4
+ Parameters
5
+
6
+ Licensor: Agimon Pty Ltd
7
+ Licensed Work: Po, the software in the Po source repository and the
8
+ packages released from it, in each version released by
9
+ the Licensor.
10
+ The Licensed Work is (c) 2026 Agimon Pty Ltd.
11
+ Material identified as third-party in the "Third-party
12
+ components" section at the end of this file or in a
13
+ NOTICE file, and any package or directory whose own
14
+ license file grants a different license, is not part of
15
+ the Licensed Work and remains under its own license.
16
+ Additional Use Grant: You may make production use of the Licensed Work if
17
+ your use is Eligible Use. "Eligible Use" is production use that falls
18
+ within at least one of the following:
19
+
20
+ (a) Personal Use. Use by an individual for their own personal purposes,
21
+ and not on behalf of, or for the benefit of, an employer, client, or
22
+ other Organization. Use by an individual in the course of employment
23
+ or engagement by an Organization is use by that Organization.
24
+
25
+ (b) Small Organization Use. Use by or on behalf of an Organization whose
26
+ Annual Revenue is less than USD 1,000,000.
27
+
28
+ (c) Non-profit, Education and Research Use. Use, regardless of Annual
29
+ Revenue, by (i) an Organization registered or recognized as a
30
+ non-profit or charitable organization under the laws of its
31
+ jurisdiction, for its non-profit purposes; (ii) an accredited school,
32
+ college, or university, for teaching, learning, or academic research;
33
+ or (iii) a publicly funded research institution, for its research.
34
+
35
+ (d) Service Provider Use. Use by a consultant, contractor, agency, or other
36
+ service provider, regardless of its own Annual Revenue, solely to
37
+ build, deploy, host, operate, or support the Licensed Work for a client
38
+ whose own use would be Eligible Use under (a), (b), or (c). A service
39
+ provider's use for its own purposes is Eligible Use only if the service
40
+ provider itself qualifies under (b) or (c).
41
+
42
+
43
+ Hosted services. Offering the Licensed Work to third parties as a hosted
44
+ or managed service whose primary value is the Licensed Work is Eligible
45
+ Use only if each such third party's own use would be Eligible Use.
46
+
47
+ Definitions. "Organization" means any company, partnership, sole
48
+ proprietorship, trust, association, government body, or other entity, and
49
+ includes an individual acting in a business or professional capacity.
50
+ "Affiliate" means any entity that directly or indirectly controls, is
51
+ controlled by, or is under common control with an Organization, where
52
+ "control" means ownership of more than 50% of the voting interests of an
53
+ entity or the power to direct its management and policies by contract or
54
+ otherwise. "Annual Revenue" means the total gross revenue of an
55
+ Organization and all of its Affiliates combined, from all sources and
56
+ whether or not related to the Licensed Work, for its most recently
57
+ completed fiscal year, or, if it has not completed a fiscal year, for the
58
+ twelve months before the use (or since its formation, if shorter),
59
+ converted into US dollars at the average exchange rate for that period.
60
+ Proceeds of equity or debt financing are not revenue.
61
+
62
+ Threshold. If an Organization stops qualifying under (b), its existing
63
+ production use remains Eligible Use for 90 days after it stops
64
+ qualifying, after which it must obtain a commercial license or stop
65
+ production use. Use that was Eligible Use when it occurred never requires
66
+ a commercial license after the fact.
67
+ Change Date: Four years from the date a version of the Licensed Work
68
+ is first publicly released by the Licensor.
69
+ Change License: Apache License, Version 2.0
70
+
71
+ For information about alternative licensing arrangements for the Licensed
72
+ Work, please visit: https://agimon.ai/licensing
73
+
74
+ Notice
75
+
76
+ The Business Source License (this document, or the "License") is not an Open
77
+ Source license. However, the Licensed Work will eventually be made available
78
+ under an Open Source License, as stated in this License.
79
+
80
+ -----------------------------------------------------------------------------
81
+
82
+ Business Source License 1.1
83
+
84
+ Terms
85
+
86
+ The Licensor hereby grants you the right to copy, modify, create derivative
87
+ works, redistribute, and make non-production use of the Licensed Work. The
88
+ Licensor may make an Additional Use Grant, above, permitting limited
89
+ production use.
90
+
91
+ Effective on the Change Date, or the fourth anniversary of the first publicly
92
+ available distribution of a specific version of the Licensed Work under this
93
+ License, whichever comes first, the Licensor hereby grants you rights under
94
+ the terms of the Change License, and the rights granted in the paragraph
95
+ above terminate.
96
+
97
+ If your use of the Licensed Work does not comply with the requirements
98
+ currently in effect as described in this License, you must purchase a
99
+ commercial license from the Licensor, its affiliated entities, or authorized
100
+ resellers, or you must refrain from using the Licensed Work.
101
+
102
+ All copies of the original and modified Licensed Work, and derivative works
103
+ of the Licensed Work, are subject to this License. This License applies
104
+ separately for each version of the Licensed Work and the Change Date may vary
105
+ for each version of the Licensed Work released by Licensor.
106
+
107
+ You must conspicuously display this License on each original or modified copy
108
+ of the Licensed Work. If you receive the Licensed Work in original or
109
+ modified form from a third party, the terms and conditions set forth in this
110
+ License apply to your use of that work.
111
+
112
+ Any use of the Licensed Work in violation of this License will automatically
113
+ terminate your rights under this License for the current and all other
114
+ versions of the Licensed Work.
115
+
116
+ This License does not grant you any right in any trademark or logo of
117
+ Licensor or its affiliates (provided that you may use a trademark or logo of
118
+ Licensor as expressly required by this License).
119
+
120
+ TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
121
+ AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
122
+ EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
123
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
124
+ TITLE.
125
+
126
+ MariaDB hereby grants you permission to use this License’s text to license
127
+ your works, and to refer to it using the trademark “Business Source License”,
128
+ as long as you comply with the Covenants of Licensor below.
129
+
130
+ Covenants of Licensor
131
+
132
+ In consideration of the right to use this License’s text and the “Business
133
+ Source License” name and trademark, Licensor covenants to MariaDB, and to all
134
+ other recipients of the licensed work to be provided by Licensor:
135
+
136
+ 1. To specify as the Change License the GPL Version 2.0 or any later version,
137
+ or a license that is compatible with GPL Version 2.0 or a later version,
138
+ where “compatible” means that software provided under the Change License can
139
+ be included in a program with software provided under GPL Version 2.0 or a
140
+ later version. Licensor may specify additional Change Licenses without
141
+ limitation.
142
+
143
+ 2. To either: (a) specify an additional grant of rights to use that does not
144
+ impose any additional restriction on the right granted in this License, as
145
+ the Additional Use Grant; or (b) insert the text “None”.
146
+
147
+ 3. To specify a Change Date.
148
+
149
+ 4. Not to modify this License in any other way.
package/README.md CHANGED
@@ -1,3 +1,162 @@
1
- # Temporary Holding Version
1
+ # Po Native
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The Po phone app for iOS and Android, built with Expo 57 and React Native 0.86. App name "Po", company iOS bundle id `ai.po.app`, Android package `ai.agimon.po`. It is private, experimental (`stage:experimental`), and built locally only: no EAS or other cloud build.
4
+
5
+ The app pairs with one Po host over its remote-access tunnel, lists that host's sessions, shows a session transcript with a composer, and runs the host's native plugin contributions (for example po-task's task tool renderer and task graph channel) from a signed composition it downloads.
6
+
7
+ ## How it works
8
+
9
+ - **Pairing.** Scan the host's pairing QR with the camera, or paste the URL. The host approves the request on its own screen. The app then registers device keys (see the trust model) and keeps the host record.
10
+ - **Connection.** Remote HTTP with the device cookie, three sealed channels (session, protocol, HTTP), the Pi `Client` over the protocol socket, and the hub socket. A dropped hub signs in again with the device key and rebuilds the connection.
11
+ - **Session state.** `@agimon-ai/po-web-dashboard/core` supplies the session, transcript, and plugin registry stores, the same ones the browser cockpit uses, without any DOM dependency.
12
+ - **Plugins.** The host advertises native compositions through `nativeGlobal` and `workspaces[].nativeComposition` on `/api/compositions` and `nativeComposition` on a session summary. For each mount the app loads the composition through its verified cache, evaluates it, keeps only the plugin ids the descriptor admits, installs them in the plugin registry, and starts them. Channel frames reach plugins through `dispatchChannelFrame`. A newer load for a mount makes older ones stale.
13
+ - **Navigation.** `App` registers screens by phase (reconnecting, pairing, ready) in a React Navigation native stack, inside `SafeAreaProvider > PoNativeProvider > KeyboardProvider > NavigationContainer`. Native headers own back navigation and swipe back; Sessions has a large title and a Settings action. A session opens in the live runtime on focus and closes on blur; Settings reloads trusted devices on focus.
14
+ - **Lists and content.** Sessions and transcripts use the component library's FlashList-backed `List`; the transcript uses `startAtBottom`. Assistant text uses `Markdown`; tool entries use the plugin `sessionToolRenderer`, falling back to `MessageItem` and `CodeBlock`.
15
+ - **Sheets.** `PoNativeProvider` supplies the shared gesture root and bottom sheet host for plugin detail sheets and action menus. The composer's decision prompts stay inline as gentle rose panels, not modal sheets. `SettingsScreen` lists trusted devices, revokes one through biometric step-up, and forgets the host.
16
+ - **Styling.** `react-native-unistyles` 3.5.2 with Po's `light` and `dark` themes from `@agimon-ai/po-web-components/nativeTheme`, registered in `src/unistyles.ts` with adaptive themes. Every screen and component keeps its `StyleSheet.create` in its own file so the Unistyles Babel plugin rewrites it.
17
+
18
+ ## Trust model
19
+
20
+ The full model is in [Security model](../../../docs/securities.md#trusted-native-devices). In short:
21
+
22
+ - **Pairing needs host approval and biometrics.** If the phone has no Face ID, Touch ID, or fingerprint enrolled, pairing stops with "Set up Face ID or a fingerprint to pair this phone."
23
+ - **Two device keys.** After approval the app generates two P-256 keys and registers them through the sealed gateway: an identity key for silent sign-in, and a step-up key that only biometrics unlock. They live in `expo-secure-store` as `po.device.identity.v1` and `po.device.stepup.v1`; the host record (origin, trusted device id, pinned signer key and revision, label) is `po.host.v1`. All three are `WHEN_UNLOCKED_THIS_DEVICE_ONLY`.
24
+ - **Reconnect.** On start the app signs in with the identity key, no QR and no host approval. It trusts the host's fresh tunnel key only after the bundle signer it pinned from the QR attests that key over the app's own nonce. A changed signer fails closed and Home shows the connect card again.
25
+ - **Step-up.** Every gated action from a device-key session needs a biometric signature, on every tunnel. `fetchWithStepUp` retries a request once after a 401 that names an action.
26
+ - **Revocation.** From the host UI, or from a paired phone after step-up (`device.revoke`). A quick-tunnel browser cannot revoke. Forget host requires successful remote revocation before deleting local keys; failure retains them for retry. A confirmed revocation is checkpointed so interrupted local cleanup can finish after restart. A biometric change invalidates the step-up key; pair again by QR.
27
+ - **Keys are software keys.** They are decrypted into JS memory to sign. They are not Secure Enclave or StrongBox keys.
28
+
29
+ ### Trusted compositions are not sandboxed
30
+
31
+ A native composition is host code, one CommonJS module per plugin. The app verifies each module before running it: on every load, cached or downloaded, it checks the signed manifest against the pinned signer with the `native` bundle profile (top-level `.js` modules, each at most 4 MiB), requires the exact advertised revision, and checks that module's hash. The same revision with a different hash is refused, a signer change clears the cache, and the cache keeps at most 64 modules under `Paths.cache/po/compositions/`. It then evaluates each module with `new Function` against a frozen map of host modules (`NATIVE_HOST_MODULE_IDS` from `@agimon-ai/po-core/native`); any other `require` throws. A module that fails to load, verify, or evaluate is left out with a warning and the rest still run. That map is a compatibility boundary, not a sandbox: a verified module runs with the app's full JavaScript privileges.
32
+
33
+ ## Local checks
34
+
35
+ ```sh
36
+ pnpm install
37
+ pnpm nx run-many -t lint typecheck build test -p @agimon-ai/po-native --parallel=2
38
+ ```
39
+
40
+ `build` runs the proof fixture builder and `expo export --platform all` into `dist/shell`. `build:proof` (`scripts/buildProof.mjs`) builds a small counter composition and signs it with the `native` profile; tests use it as a fixture. Node tests do not exercise native crypto, Hermes, or native modules.
41
+
42
+ ## Running on a device
43
+
44
+ ```sh
45
+ pnpm nx run @agimon-ai/po-native:prebuild
46
+ pnpm --filter @agimon-ai/po-native ios:setup
47
+ pnpm nx run @agimon-ai/po-native:ios
48
+ JAVA_HOME=$(/usr/libexec/java_home -v 17) pnpm nx run @agimon-ai/po-native:android
49
+ ```
50
+
51
+ `ios` and `android` are local Release builds. `ios:setup` installs CocoaPods 1.16.2 under `.build/gems` and the iOS commands set `GEM_HOME`, `GEM_PATH`, and `PATH` for that process only; system gems and shell configuration are untouched. The generated `ios/` and `android/` folders, Expo state, and `.build/` are gitignored and never edited by hand; signing changes come from `@agimon-ai/po-expo-signing` during prebuild.
52
+
53
+ The company iOS build requires an `ai.po.app` provisioning profile with Push Notifications. Previous
54
+ `ai.agimon.po` iOS installs are a separate app identity; rebuild and pair the company app. Native push
55
+ checks the installed iOS application ID before enrollment and reads its provisioning-profile APNs
56
+ environment, with a production fallback for App Store/TestFlight installs.
57
+ Android remains `ai.agimon.po` with FCM. See [push setup](../../foundations/po-notification/README.md#native-push-server-credentials).
58
+
59
+ ## Ad hoc builds
60
+
61
+ ```sh
62
+ pnpm nx run @agimon-ai/po-native:ios:adhoc
63
+ pnpm nx run @agimon-ai/po-native:android:adhoc
64
+ ```
65
+
66
+ Both run `scripts/buildAdHoc.mjs`, which fails before touching earlier outputs when a required variable is missing. Every command runs through `spawnSync` with an argument array, and secret values are redacted from console output and logs.
67
+
68
+ - **iOS** (macOS with Xcode): build the proof fixture, `expo prebuild --clean --platform ios`, `pod install`, `xcodebuild archive`, then `-exportArchive` with a temporary `ExportOptions.plist` (`method: release-testing`, manual signing) that is deleted afterwards. Run `ios:setup` first.
69
+ - **Android**: build the proof fixture, `expo prebuild --clean --platform android`, then `./gradlew assembleRelease` under JDK 17.
70
+
71
+ | Variable | Platform | Required | Meaning |
72
+ | ------------------------------------- | -------- | -------- | ------------------------------------------------------------------------------- |
73
+ | `APPLE_TEAM_ID` | iOS | yes | Team id for `appleTeamId`, `DEVELOPMENT_TEAM`, and the export plist |
74
+ | `PO_NATIVE_IOS_PROFILE` | iOS | yes | Provisioning profile name; the Release target switches to manual signing |
75
+ | `PO_NATIVE_IOS_CODE_SIGN_IDENTITY` | iOS | no | `CODE_SIGN_IDENTITY` and export `signingCertificate`, e.g. `Apple Distribution` |
76
+ | `PO_NATIVE_ANDROID_KEYSTORE` | Android | yes | Path to the release keystore, made absolute before Gradle runs |
77
+ | `PO_NATIVE_ANDROID_KEYSTORE_PASSWORD` | Android | yes | Keystore password |
78
+ | `PO_NATIVE_ANDROID_KEY_ALIAS` | Android | yes | Key alias |
79
+ | `PO_NATIVE_ANDROID_KEY_PASSWORD` | Android | yes | Key password |
80
+ | `JAVA_HOME` | Android | no | JDK 17; on macOS it defaults to `/usr/libexec/java_home -v 17` |
81
+
82
+ Secrets come only from these variables or the keychain. Gradle reads the Android values at build time, so none is written into the generated project.
83
+
84
+ **Output.** `.build/adhoc/po-native.ipa` or `.build/adhoc/po-native.apk`, plus `.build/adhoc/SHA256SUMS` covering whichever artifacts exist. Per-step logs (mode `0600`) go to `.build/adhoc/logs/<platform>/`, and intermediate files to `.build/adhoc/work/<platform>/`.
85
+
86
+ ## TestFlight archive and export
87
+
88
+ Use an installed **App Store Connect distribution profile** for `ai.po.app`, with Push Notifications,
89
+ and the matching **Apple Distribution certificate and private key** in the local keychain. A development
90
+ or ad hoc profile is not interchangeable. Run `ios:setup` first. No App Store Connect API key is needed
91
+ for this local export.
92
+
93
+ ```sh
94
+ export APPLE_TEAM_ID="RYQ366PW4R"
95
+ export PO_NATIVE_IOS_PROFILE="YOUR_APP_STORE_CONNECT_PROFILE"
96
+ export PO_NATIVE_IOS_CODE_SIGN_IDENTITY="Apple Distribution"
97
+ export PO_NATIVE_IOS_BUILD_NUMBER="42"
98
+ export PO_NATIVE_APNS_ENVIRONMENT="production"
99
+ pnpm nx run @agimon-ai/po-native:ios:testflight
100
+ ```
101
+
102
+ The same command is available as `pnpm --filter @agimon-ai/po-native ios:testflight`. It reuses the
103
+ local iOS archive script, defaults the signing identity to `Apple Distribution`, and supplies production
104
+ APNs to prebuild. An explicitly configured sandbox environment is rejected before building.
105
+ `PO_NATIVE_IOS_BUILD_NUMBER` is required, becomes Expo's `ios.buildNumber`, and must increase for each
106
+ submitted build. Use a valid `CFBundleVersion`, for example `42` or `1.2.3` (first component 1-9999,
107
+ remaining components 0-99). The marketing version remains `version` in `app.config.ts`.
108
+
109
+ Before export, the script verifies the archive's bundle ID, build number and signed production APNs
110
+ entitlement. It exports with `method: app-store-connect`, `destination: export`, manual signing, and
111
+ `manageAppVersionAndBuildNumber: false`. It does not upload, submit, install, create credentials, or
112
+ modify the Apple portal. Upload remains a separate, explicitly approved action.
113
+
114
+ TestFlight installs have no embedded provisioning profile. When the profile environment is unavailable,
115
+ the app checks the native release type and registers `production` only for App Store/TestFlight builds.
116
+ Other release types still require an explicit profile environment. After pairing and allowing notifications,
117
+ open the app and verify `nativePush.environment: "production"` on its trusted device record in the
118
+ server's `~/.po/web/trusted-devices.json`. Do not print or share the token. This confirms enrollment,
119
+ not APNs delivery.
120
+
121
+ **Output:** `.build/testflight/po-native.ipa` and `.build/testflight/SHA256SUMS`. The archive and dSYMs
122
+ remain under `.build/testflight/work/ios/`, with private per-step logs under `.build/testflight/logs/ios/`.
123
+ Existing ad hoc and Android commands keep their methods and `.build/adhoc/` outputs. A failed build
124
+ keeps the previous final IPA/APK. Command/configuration tests do not verify real signing, App Store
125
+ Connect acceptance, or TestFlight push delivery.
126
+
127
+ ## Native stories
128
+
129
+ Each screen has a story beside it (`src/screens/*.stories.tsx`), and po-task has three `*.native.stories.tsx` stories for its native task UI. They are tagged `style-system`, `mockup`, and `native`. They render through `@agimon-ai/style-system`'s native mode: this package's `style-system.config.yaml` extends the root `native-app` preset (`bundler: vite-react-native`, `unistylesConfig: src/unistyles.ts`, dark scheme, 393 by 852 viewport). Whether style-system accepts po-task's stories, which live in another package, when rendered against this app path is unverified.
130
+
131
+ ## Not yet
132
+
133
+ - **Activity groups are not rendered.** Only the plain host `activity` slot is; `activity.<group>` fills such as po-task's task section do not appear.
134
+ - **Threads, transient tabs, and composer context are inert placeholders.** The slot props the app passes to plugins stub `renderThread`, `openTransientTab`, `closeTransientTab`, `appendComposerDraft`, `attachComposerContext`, and `attachComposerCapture`; `sendSessionFrame`, `renderSlot`, and `slotData` are inert there too.
135
+ - **A session's composition mounts only if its summary is loaded when the session is opened.** A summary that arrives later does not mount it.
136
+ - **Not ported from po-web:** alerts, voice handoff, dormant transcripts, thread frames, and held channels.
137
+ - **`po doctor` does not show the native build.** `po sync --check` does.
138
+ - **Device runtime is unverified.** No release-mode device run has confirmed that Hermes runs `new Function`, that `react-native-quick-crypto` WebCrypto ECDSA produces the P1363 signatures the host verifies, that `expo-secure-store` biometric items prompt and invalidate as expected, or that the `expo-camera` scanner works.
139
+ - **Visual fidelity is BLOCKED.** The screens and stories are mockups (`mockup` tag). Rendered appearance needs human approval of the native stories before it can be claimed.
140
+ - **One paired host per phone.**
141
+
142
+ ## Phase 3 native grants
143
+
144
+ `accountGrants` exposes `onSessionStart` and `onJobCreated` typed hooks. Same-host work returns
145
+ no grant and does not contact the account service. Cross-host work gets a 30-day, non-redelegable
146
+ C5 signed by the account device key with the issuer's current revocation epoch. SecureStore keeps
147
+ envelopes in issuer-specific namespaces. Opening or foregrounding the app renews unexpired grants
148
+ only inside the seven-day window. An epoch change never silently renews old consent. An explicit
149
+ renew action may obtain new consent after refreshing account trust.
150
+
151
+ `receiveGrantPause` accepts the typed work-paused event plus the authenticated reporting host id,
152
+ matches it to saved work, and supplies Home's gentle rose notice. `startLiveRuntime` accepts an
153
+ optional network-authenticated `federationHostId`. The local pairing record id is not that id.
154
+
155
+ Cross-host session/job creation and network host-id mapping are pending phases 5 and 7. Those
156
+ callers must pass the returned envelopes to the calling host, which stores them and refreshes
157
+ delegated tokens. App-open renewal currently updates the phone's saved envelopes only; host
158
+ handoff and automatic job resume are not mounted yet. No public host upload route is invented.
159
+ The notice deliberately says permission was renewed on this phone, not that a job resumed.
160
+
161
+ Notice targets: `Native/Design/Grants`, Playground, Dark, Renewing and Retry. These remain mockups.
162
+ Visual fidelity is BLOCKED pending explicit user approval, and native device behavior is unverified.
package/app.config.ts ADDED
@@ -0,0 +1,84 @@
1
+ import type { ExpoConfig } from 'expo/config';
2
+
3
+ const apnsEnvironment = process.env.PO_NATIVE_APNS_ENVIRONMENT ?? 'development';
4
+ if (apnsEnvironment !== 'development' && apnsEnvironment !== 'production') {
5
+ throw new Error('PO_NATIVE_APNS_ENVIRONMENT must be development or production.');
6
+ }
7
+
8
+ const iosBuildNumber = process.env.PO_NATIVE_IOS_BUILD_NUMBER?.trim();
9
+ if (iosBuildNumber !== undefined && !/^[1-9]\d{0,3}(?:\.\d{1,2}){0,2}$/u.test(iosBuildNumber)) {
10
+ throw new Error('PO_NATIVE_IOS_BUILD_NUMBER must be a valid CFBundleVersion (for example 42).');
11
+ }
12
+ // A production build must name its identity home explicitly; the default issuer is for development only.
13
+ if (apnsEnvironment === 'production' && !process.env.PO_NATIVE_IDENTITY_ISSUER?.trim()) {
14
+ throw new Error('PO_NATIVE_IDENTITY_ISSUER must be set when PO_NATIVE_APNS_ENVIRONMENT is production.');
15
+ }
16
+ const identityIssuer = new URL(process.env.PO_NATIVE_IDENTITY_ISSUER ?? 'https://po.agimon.win');
17
+ if (
18
+ identityIssuer.protocol !== 'https:' ||
19
+ identityIssuer.username ||
20
+ identityIssuer.password ||
21
+ identityIssuer.pathname !== '/' ||
22
+ identityIssuer.search ||
23
+ identityIssuer.hash
24
+ ) {
25
+ throw new Error('PO_NATIVE_IDENTITY_ISSUER must be an HTTPS origin.');
26
+ }
27
+ const config: ExpoConfig = {
28
+ name: 'Po',
29
+ slug: 'po',
30
+ scheme: 'po',
31
+ extra: { identityIssuer: identityIssuer.origin },
32
+ version: '0.0.1',
33
+ icon: './assets/icon.png',
34
+ orientation: 'portrait',
35
+ platforms: ['ios', 'android'],
36
+ ios: {
37
+ bundleIdentifier: 'ai.po.app',
38
+ usesAppleSignIn: true,
39
+ // Export compliance answer on App Store Connect: no non-exempt encryption.
40
+ config: { usesNonExemptEncryption: false },
41
+ ...(iosBuildNumber ? { buildNumber: iosBuildNumber } : {}),
42
+ ...(process.env.APPLE_TEAM_ID ? { appleTeamId: process.env.APPLE_TEAM_ID } : {}),
43
+ },
44
+ android: {
45
+ package: 'ai.agimon.po',
46
+ ...(process.env.PO_NATIVE_ANDROID_GOOGLE_SERVICES_FILE
47
+ ? { googleServicesFile: process.env.PO_NATIVE_ANDROID_GOOGLE_SERVICES_FILE }
48
+ : {}),
49
+ blockedPermissions: ['android.permission.READ_EXTERNAL_STORAGE', 'android.permission.WRITE_EXTERNAL_STORAGE'],
50
+ },
51
+ plugins: [
52
+ 'expo-apple-authentication',
53
+ ['expo-notifications', { mode: apnsEnvironment, defaultChannel: 'po-notifications' }],
54
+ ['expo-secure-store', { faceIDPermission: 'Confirm it is you before Po does something important.' }],
55
+ ['expo-camera', { cameraPermission: 'Scan a Po pairing QR code.' }],
56
+ [
57
+ 'expo-image-picker',
58
+ {
59
+ photosPermission: 'Attach photos to your message for Po.',
60
+ cameraPermission: false,
61
+ },
62
+ ],
63
+ [
64
+ 'react-native-audio-api',
65
+ {
66
+ iosMicrophonePermission: 'Record your voice for Po while you choose to record.',
67
+ iosBackgroundMode: true,
68
+ androidPermissions: [
69
+ 'android.permission.RECORD_AUDIO',
70
+ 'android.permission.FOREGROUND_SERVICE',
71
+ 'android.permission.FOREGROUND_SERVICE_MICROPHONE',
72
+ 'android.permission.POST_NOTIFICATIONS',
73
+ ],
74
+ androidForegroundService: true,
75
+ androidFSTypes: ['microphone'],
76
+ disableFFmpeg: true,
77
+ },
78
+ ],
79
+ '@config-plugins/react-native-webrtc',
80
+ '@agimon-ai/po-expo-signing',
81
+ ],
82
+ };
83
+
84
+ export default config;
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,8 @@
1
+ // babel-preset-expo already registers the react-native-worklets plugin; do not add it here.
2
+ module.exports = function babelConfig(api) {
3
+ api.cache(true);
4
+ return {
5
+ presets: ['babel-preset-expo'],
6
+ plugins: [['react-native-unistyles/plugin', { root: 'src' }]],
7
+ };
8
+ };